From 5bd32fc0668ca7a6ab47d9fa7b2968243a55fcce Mon Sep 17 00:00:00 2001 From: William Martin Date: Mon, 18 Mar 2024 12:17:12 +0100 Subject: [PATCH 01/26] Bump go-keyring to fix race condition --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 1f0610ebc..cc4cbe6fa 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,7 @@ require ( github.com/spf13/cobra v1.6.1 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.4 - github.com/zalando/go-keyring v0.2.3 + github.com/zalando/go-keyring v0.2.4 golang.org/x/crypto v0.17.0 golang.org/x/sync v0.1.0 golang.org/x/term v0.15.0 @@ -48,12 +48,12 @@ require ( require ( github.com/alecthomas/chroma v0.10.0 // indirect - github.com/alessio/shellescape v1.4.1 // indirect + github.com/alessio/shellescape v1.4.2 // indirect github.com/aymanbagabas/go-osc52 v1.0.3 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/cli/browser v1.3.0 // indirect github.com/cli/shurcooL-graphql v0.0.4 // indirect - github.com/danieljoos/wincred v1.2.0 // indirect + github.com/danieljoos/wincred v1.2.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dlclark/regexp2 v1.4.0 // indirect github.com/fatih/color v1.7.0 // indirect @@ -83,7 +83,7 @@ require ( github.com/yuin/goldmark v1.5.2 // indirect github.com/yuin/goldmark-emoji v1.0.1 // indirect golang.org/x/net v0.17.0 // indirect - golang.org/x/sys v0.15.0 // indirect + golang.org/x/sys v0.18.0 // indirect google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect ) diff --git a/go.sum b/go.sum index 0ac2b9277..ef96d7e4e 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,8 @@ github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63n github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek= github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= -github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= -github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= +github.com/alessio/shellescape v1.4.2 h1:MHPfaU+ddJ0/bYWpgIeUnQUqKrlJ1S7BfEYPM4uEoM0= +github.com/alessio/shellescape v1.4.2/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= github.com/aymanbagabas/go-osc52 v1.0.3 h1:DTwqENW7X9arYimJrPeGZcV0ln14sGMt3pHZspWD+Mg= github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= @@ -39,8 +39,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0= github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/danieljoos/wincred v1.2.0 h1:ozqKHaLK0W/ii4KVbbvluM91W2H3Sh0BncbUNPS7jLE= -github.com/danieljoos/wincred v1.2.0/go.mod h1:FzQLLMKBFdvu+osBrnFODiv32YGwCfx0SkRa/eYHgec= +github.com/danieljoos/wincred v1.2.1 h1:dl9cBrupW8+r5250DYkYxocLeZ1Y4vB1kxgtjxw8GQs= +github.com/danieljoos/wincred v1.2.1/go.mod h1:uGaFL9fDn3OLTvzCGulzE+SzjEe5NGlh5FdCcyfPwps= 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= @@ -170,8 +170,8 @@ github.com/yuin/goldmark v1.5.2 h1:ALmeCk/px5FSm1MAcFBAsVKZjDuMVj8Tm7FFIlMJnqU= github.com/yuin/goldmark v1.5.2/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark-emoji v1.0.1 h1:ctuWEyzGBwiucEqxzwe0SOYDXPAucOrE9NQC18Wa1os= github.com/yuin/goldmark-emoji v1.0.1/go.mod h1:2w1E6FEWLcDQkoTE+7HU6QF1F6SLlNGjRIBbIZQFqkQ= -github.com/zalando/go-keyring v0.2.3 h1:v9CUu9phlABObO4LPWycf+zwMG7nlbb3t/B5wa97yms= -github.com/zalando/go-keyring v0.2.3/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk= +github.com/zalando/go-keyring v0.2.4 h1:wi2xxTqdiwMKbM6TWwi+uJCG/Tum2UV0jqaQhCa9/68= +github.com/zalando/go-keyring v0.2.4/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk= 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.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= @@ -200,8 +200,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= From 304ce82fa7c3e95f44b2095eb1e5c849c8510f6f Mon Sep 17 00:00:00 2001 From: Josh Soref <2119212+jsoref@users.noreply.github.com> Date: Mon, 18 Mar 2024 11:21:25 -0400 Subject: [PATCH 02/26] grammar: avoid e.g.+etc. together https://meryl.net/ie-etc-and-eg/ --- docs/multiple-accounts.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/multiple-accounts.md b/docs/multiple-accounts.md index 67b6fa0b5..29c83b703 100644 --- a/docs/multiple-accounts.md +++ b/docs/multiple-accounts.md @@ -8,7 +8,7 @@ A particular shoutout to @gabe565 and his long term community support for https: With the release of `v2.40.0`, `gh` has begun supporting multiple accounts for some use cases on github.com and in GitHub Enterprise. We recognise that there are a number of missing quality of life features, and we've opted -not to address the use case of automatic account switching based on some context (e.g. `pwd`, `git remote`, etc). +not to address the use case of automatic account switching based on some context (e.g. `pwd`, `git remote`). However, we hope many of those using these custom solutions will now find it easier to obtain and update tokens (via the standard OAuth flow rather than as a PAT), and to store them securely in the system keyring managed by `gh`. @@ -111,7 +111,7 @@ if there are any remaining logged into the host: As mentioned above, we know that this only addresses some of the requests around supporting multiple accounts. While these are not out of scope forever, for this release some of the big things we have intentionally not included are: - * Automatic account switching based on some context (e.g. `pwd`, `git remote`, etc) + * Automatic account switching based on some context (e.g. `pwd`, `git remote`) * Automatic configuration of git config such as `user.name` and `user.email` when switching * User level configuration e.g. `williammartin` uses `vim` but `wilmartin_microsoft` uses `emacs` From 6ff355049c96d169cc8ffbd9aaec32c19dce41ca Mon Sep 17 00:00:00 2001 From: Josh Soref <2119212+jsoref@users.noreply.github.com> Date: Mon, 18 Mar 2024 12:09:27 -0400 Subject: [PATCH 03/26] Improve discovery of `gh auth status` --- pkg/cmd/auth/refresh/refresh.go | 6 +++++- pkg/cmd/auth/status/status.go | 2 +- pkg/cmd/auth/switch/switch.go | 6 ++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/pkg/cmd/auth/refresh/refresh.go b/pkg/cmd/auth/refresh/refresh.go index a918493e8..b77c45bac 100644 --- a/pkg/cmd/auth/refresh/refresh.go +++ b/pkg/cmd/auth/refresh/refresh.go @@ -55,7 +55,7 @@ func NewCmdRefresh(f *cmdutil.Factory, runF func(*RefreshOptions) error) *cobra. Use: "refresh", Args: cobra.ExactArgs(0), Short: "Refresh stored authentication credentials", - Long: heredoc.Docf(`Expand or fix the permission scopes for stored credentials. + Long: heredoc.Docf(`Expand or fix the permission scopes for stored credentials for active account. The %[1]s--scopes%[1]s flag accepts a comma separated list of scopes you want your gh credentials to have. If no scopes are provided, the command @@ -67,6 +67,10 @@ func NewCmdRefresh(f *cmdutil.Factory, runF func(*RefreshOptions) error) *cobra. The %[1]s--reset-scopes%[1]s flag resets the scopes for your gh credentials to the default set of scopes for your auth flow. + + If you have multiple accounts in %[1]sgh auth status%[1]s and want to refresh the credentials for an + inactive account, you will have to use %[1]sgh auth switch%[1]s to that account first before using + this command, and then switch back when you are done. `, "`"), Example: heredoc.Doc(` $ gh auth refresh --scopes write:org,read:public_key diff --git a/pkg/cmd/auth/status/status.go b/pkg/cmd/auth/status/status.go index fa027e193..c28c3f48e 100644 --- a/pkg/cmd/auth/status/status.go +++ b/pkg/cmd/auth/status/status.go @@ -140,7 +140,7 @@ func NewCmdStatus(f *cmdutil.Factory, runF func(*StatusOptions) error) *cobra.Co cmd := &cobra.Command{ Use: "status", Args: cobra.ExactArgs(0), - Short: "View authentication status", + Short: "View all accounts and authentication status", Long: heredoc.Doc(`Verifies and displays information about your authentication state. This command will test your authentication state for each GitHub host that gh knows about and diff --git a/pkg/cmd/auth/switch/switch.go b/pkg/cmd/auth/switch/switch.go index 129fe9fd4..bc94ff8c4 100644 --- a/pkg/cmd/auth/switch/switch.go +++ b/pkg/cmd/auth/switch/switch.go @@ -32,12 +32,14 @@ func NewCmdSwitch(f *cmdutil.Factory, runF func(*SwitchOptions) error) *cobra.Co Use: "switch", Args: cobra.ExactArgs(0), Short: "Switch active GitHub account", - Long: heredoc.Doc(` + Long: heredoc.Docf(` Switch the active account for a GitHub host. This command changes the authentication configuration that will be used when running commands targeting the specified GitHub host. - `), + + For a list of authenticated accounts you can run %[1]sgh auth status%[1]s. + `, "`"), Example: heredoc.Doc(` # Select what host and account to switch to via a prompt $ gh auth switch From 0f5d2aed0aef023fc10d618895f978ec836cb830 Mon Sep 17 00:00:00 2001 From: William Martin Date: Mon, 18 Mar 2024 17:24:38 +0100 Subject: [PATCH 04/26] Document auth switch behaviour for two or more accounts --- pkg/cmd/auth/switch/switch.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/cmd/auth/switch/switch.go b/pkg/cmd/auth/switch/switch.go index bc94ff8c4..cc2cd5b26 100644 --- a/pkg/cmd/auth/switch/switch.go +++ b/pkg/cmd/auth/switch/switch.go @@ -38,6 +38,10 @@ func NewCmdSwitch(f *cmdutil.Factory, runF func(*SwitchOptions) error) *cobra.Co This command changes the authentication configuration that will be used when running commands targeting the specified GitHub host. + If the specified host has two accounts, the active account will be switched + automatically. If there are more than two accounts, disambiguation will be + required either through the %[1]s--user%[1]s flag or an interactive prompt. + For a list of authenticated accounts you can run %[1]sgh auth status%[1]s. `, "`"), Example: heredoc.Doc(` From 90fc4038137e3a9c0f4821e0b9aca711cb5eef22 Mon Sep 17 00:00:00 2001 From: William Martin Date: Tue, 19 Mar 2024 14:52:43 +0100 Subject: [PATCH 05/26] Document run watch and view not supporting fine grained PATs --- pkg/cmd/run/view/view.go | 8 +++++++- pkg/cmd/run/watch/watch.go | 6 ++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/pkg/cmd/run/view/view.go b/pkg/cmd/run/view/view.go index d6cb008d0..c54905422 100644 --- a/pkg/cmd/run/view/view.go +++ b/pkg/cmd/run/view/view.go @@ -96,7 +96,13 @@ func NewCmdView(f *cmdutil.Factory, runF func(*ViewOptions) error) *cobra.Comman cmd := &cobra.Command{ Use: "view []", Short: "View a summary of a workflow run", - Args: cobra.MaximumNArgs(1), + Long: heredoc.Docf(` + View a summary of a workflow run. + + This command does not support authenticating via fine grained PATs + as it is not currently possible to create a PAT with the %[1]schecks:read%[1]s permission. + `, "`"), + Args: cobra.MaximumNArgs(1), Example: heredoc.Doc(` # Interactively select a run to view, optionally selecting a single job $ gh run view diff --git a/pkg/cmd/run/watch/watch.go b/pkg/cmd/run/watch/watch.go index b4b51f20c..92120dc38 100644 --- a/pkg/cmd/run/watch/watch.go +++ b/pkg/cmd/run/watch/watch.go @@ -45,6 +45,12 @@ func NewCmdWatch(f *cmdutil.Factory, runF func(*WatchOptions) error) *cobra.Comm cmd := &cobra.Command{ Use: "watch ", Short: "Watch a run until it completes, showing its progress", + Long: heredoc.Docf(` + Watch a run until it completes, showing its progress. + + This command does not support authenticating via fine grained PATs + as it is not currently possible to create a PAT with the %[1]schecks:read%[1]s permission. + `, "`"), Example: heredoc.Doc(` # Watch a run until it's done gh run watch From 911d96688078eb44fd8bd04d8b4d0cd805c0a332 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Mar 2024 14:13:27 +0000 Subject: [PATCH 06/26] build(deps): bump github.com/cpuguy83/go-md2man/v2 from 2.0.3 to 2.0.4 Bumps [github.com/cpuguy83/go-md2man/v2](https://github.com/cpuguy83/go-md2man) from 2.0.3 to 2.0.4. - [Release notes](https://github.com/cpuguy83/go-md2man/releases) - [Commits](https://github.com/cpuguy83/go-md2man/compare/v2.0.3...v2.0.4) --- updated-dependencies: - dependency-name: github.com/cpuguy83/go-md2man/v2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index cc4cbe6fa..d6dc68136 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/cli/go-gh/v2 v2.6.0 github.com/cli/oauth v1.0.1 github.com/cli/safeexec v1.0.1 - github.com/cpuguy83/go-md2man/v2 v2.0.3 + github.com/cpuguy83/go-md2man/v2 v2.0.4 github.com/creack/pty v1.1.21 github.com/gabriel-vasile/mimetype v1.4.3 github.com/gdamore/tcell/v2 v2.5.4 diff --git a/go.sum b/go.sum index ef96d7e4e..0a4a6e8f9 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,8 @@ github.com/cli/safeexec v1.0.1/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5 github.com/cli/shurcooL-graphql v0.0.4 h1:6MogPnQJLjKkaXPyGqPRXOI2qCsQdqNfUY1QSJu2GuY= github.com/cli/shurcooL-graphql v0.0.4/go.mod h1:3waN4u02FiZivIV+p1y4d0Jo1jc6BViMA73C+sZo2fk= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= -github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0= From b54f7a3bde50df3c31fdd68b638a0c0378a0ad58 Mon Sep 17 00:00:00 2001 From: yasunori Date: Wed, 20 Mar 2024 20:58:51 +0900 Subject: [PATCH 07/26] Include draft ID in project item list JSON (#8754) --- pkg/cmd/project/item-list/item_list_test.go | 7 ++++++- pkg/cmd/project/shared/queries/export_data_test.go | 9 ++++++--- pkg/cmd/project/shared/queries/queries.go | 1 + 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/pkg/cmd/project/item-list/item_list_test.go b/pkg/cmd/project/item-list/item_list_test.go index 58d4beeeb..e3f10e655 100644 --- a/pkg/cmd/project/item-list/item_list_test.go +++ b/pkg/cmd/project/item-list/item_list_test.go @@ -164,6 +164,7 @@ func TestRunList_User_tty(t *testing.T) { { "id": "draft issue ID", "content": map[string]interface{}{ + "id": "draft issue ID", "title": "draft issue", "__typename": "DraftIssue", }, @@ -273,6 +274,7 @@ func TestRunList_User(t *testing.T) { { "id": "draft issue ID", "content": map[string]interface{}{ + "id": "draft issue ID", "title": "draft issue", "__typename": "DraftIssue", }, @@ -379,6 +381,7 @@ func TestRunList_Org(t *testing.T) { { "id": "draft issue ID", "content": map[string]interface{}{ + "id": "draft issue ID", "title": "draft issue", "__typename": "DraftIssue", }, @@ -475,6 +478,7 @@ func TestRunList_Me(t *testing.T) { { "id": "draft issue ID", "content": map[string]interface{}{ + "id": "draft issue ID", "title": "draft issue", "__typename": "DraftIssue", }, @@ -581,6 +585,7 @@ func TestRunList_JSON(t *testing.T) { { "id": "draft issue ID", "content": map[string]interface{}{ + "id": "draft issue ID", "title": "draft issue", "__typename": "DraftIssue", }, @@ -610,6 +615,6 @@ func TestRunList_JSON(t *testing.T) { assert.NoError(t, err) assert.JSONEq( t, - `{"items":[{"content":{"type":"Issue","body":"","title":"an issue","number":1,"repository":"cli/go-gh","url":""},"id":"issue ID"},{"content":{"type":"PullRequest","body":"","title":"a pull request","number":2,"repository":"cli/go-gh","url":""},"id":"pull request ID"},{"content":{"type":"DraftIssue","body":"","title":"draft issue"},"id":"draft issue ID"}],"totalCount":3}`, + `{"items":[{"content":{"type":"Issue","body":"","title":"an issue","number":1,"repository":"cli/go-gh","url":""},"id":"issue ID"},{"content":{"type":"PullRequest","body":"","title":"a pull request","number":2,"repository":"cli/go-gh","url":""},"id":"pull request ID"},{"content":{"type":"DraftIssue","body":"","title":"draft issue","id":"draft issue ID"},"id":"draft issue ID"}],"totalCount":3}`, stdout.String()) } diff --git a/pkg/cmd/project/shared/queries/export_data_test.go b/pkg/cmd/project/shared/queries/export_data_test.go index 976839d43..adfdce63f 100644 --- a/pkg/cmd/project/shared/queries/export_data_test.go +++ b/pkg/cmd/project/shared/queries/export_data_test.go @@ -259,6 +259,7 @@ func TestJSONProjectDetailedItems(t *testing.T) { Content: ProjectItemContent{ TypeName: "DraftIssue", DraftIssue: DraftIssue{ + ID: "draftIssueId", Title: "Pull Request title", Body: "a body", }, @@ -270,7 +271,7 @@ func TestJSONProjectDetailedItems(t *testing.T) { assert.NoError(t, err) assert.JSONEq( t, - `{"items":[{"content":{"type":"Issue","body":"a body","title":"Issue title","number":1,"repository":"cli/go-gh","url":"issue-url"},"id":"issueId"},{"content":{"type":"PullRequest","body":"a body","title":"Pull Request title","number":2,"repository":"cli/go-gh","url":"pr-url"},"id":"pullRequestId"},{"content":{"type":"DraftIssue","body":"a body","title":"Pull Request title"},"id":"draftIssueId"}],"totalCount":5}`, + `{"items":[{"content":{"type":"Issue","body":"a body","title":"Issue title","number":1,"repository":"cli/go-gh","url":"issue-url"},"id":"issueId"},{"content":{"type":"PullRequest","body":"a body","title":"Pull Request title","number":2,"repository":"cli/go-gh","url":"pr-url"},"id":"pullRequestId"},{"content":{"type":"DraftIssue","body":"a body","title":"Pull Request title","id":"draftIssueId"},"id":"draftIssueId"}],"totalCount":5}`, string(out)) } @@ -300,6 +301,7 @@ func TestJSONProjectItem_DraftIssue_ProjectV2ItemFieldIterationValue(t *testing. Content: ProjectItemContent{ TypeName: "DraftIssue", DraftIssue: DraftIssue{ + ID: "draftIssueId", Title: "Pull Request title", Body: "a body", }, @@ -319,7 +321,7 @@ func TestJSONProjectItem_DraftIssue_ProjectV2ItemFieldIterationValue(t *testing. assert.NoError(t, err) assert.JSONEq( t, - `{"items":[{"sprint":{"title":"Iteration Title","startDate":"","duration":0},"content":{"type":"DraftIssue","body":"a body","title":"Pull Request title"},"id":"draftIssueId"}],"totalCount":5}`, + `{"items":[{"sprint":{"title":"Iteration Title","startDate":"","duration":0},"content":{"type":"DraftIssue","body":"a body","title":"Pull Request title","id":"draftIssueId"},"id":"draftIssueId"}],"totalCount":5}`, string(out)) } @@ -338,6 +340,7 @@ func TestJSONProjectItem_DraftIssue_ProjectV2ItemFieldMilestoneValue(t *testing. Content: ProjectItemContent{ TypeName: "DraftIssue", DraftIssue: DraftIssue{ + ID: "draftIssueId", Title: "Pull Request title", Body: "a body", }, @@ -357,7 +360,7 @@ func TestJSONProjectItem_DraftIssue_ProjectV2ItemFieldMilestoneValue(t *testing. assert.NoError(t, err) assert.JSONEq( t, - `{"items":[{"milestone":{"title":"Milestone Title","dueOn":"","description":""},"content":{"type":"DraftIssue","body":"a body","title":"Pull Request title"},"id":"draftIssueId"}],"totalCount":5}`, + `{"items":[{"milestone":{"title":"Milestone Title","dueOn":"","description":""},"content":{"type":"DraftIssue","body":"a body","title":"Pull Request title","id":"draftIssueId"},"id":"draftIssueId"}],"totalCount":5}`, string(out)) } diff --git a/pkg/cmd/project/shared/queries/queries.go b/pkg/cmd/project/shared/queries/queries.go index e54a3fb17..080a43097 100644 --- a/pkg/cmd/project/shared/queries/queries.go +++ b/pkg/cmd/project/shared/queries/queries.go @@ -400,6 +400,7 @@ func (p ProjectItem) DetailedItem() exportable { switch p.Type() { case "DraftIssue": return DraftIssue{ + ID: p.Content.DraftIssue.ID, Body: p.Body(), Title: p.Title(), } From 1cac2280aab1288e84e04fd0336560f89bf140c0 Mon Sep 17 00:00:00 2001 From: ihommani Date: Sat, 23 Mar 2024 20:56:09 +0000 Subject: [PATCH 08/26] Fix typo in auth switch help example --- pkg/cmd/auth/switch/switch.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cmd/auth/switch/switch.go b/pkg/cmd/auth/switch/switch.go index cc2cd5b26..d2aa73a9d 100644 --- a/pkg/cmd/auth/switch/switch.go +++ b/pkg/cmd/auth/switch/switch.go @@ -49,7 +49,7 @@ func NewCmdSwitch(f *cmdutil.Factory, runF func(*SwitchOptions) error) *cobra.Co $ gh auth switch # Switch to a specific host and specific account - $ gh auth logout --hostname enterprise.internal --user monalisa + $ gh auth switch --hostname enterprise.internal --user monalisa `), RunE: func(c *cobra.Command, args []string) error { if runF != nil { From fc2dabac6e9501f9bace63b29b10ae2e0ae1849a Mon Sep 17 00:00:00 2001 From: William Martin Date: Thu, 28 Mar 2024 21:45:49 +0100 Subject: [PATCH 09/26] Bump go-gh to 2.7.0 Brings across a better CacheDir --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index d6dc68136..3d432895c 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/cenkalti/backoff/v4 v4.2.1 github.com/charmbracelet/glamour v0.6.0 github.com/charmbracelet/lipgloss v0.5.0 - github.com/cli/go-gh/v2 v2.6.0 + github.com/cli/go-gh/v2 v2.7.0 github.com/cli/oauth v1.0.1 github.com/cli/safeexec v1.0.1 github.com/cpuguy83/go-md2man/v2 v2.0.4 diff --git a/go.sum b/go.sum index 0a4a6e8f9..c05d673cd 100644 --- a/go.sum +++ b/go.sum @@ -23,8 +23,8 @@ github.com/charmbracelet/lipgloss v0.5.0/go.mod h1:EZLha/HbzEt7cYqdFPovlqy5FZPj0 github.com/cli/browser v1.0.0/go.mod h1:IEWkHYbLjkhtjwwWlwTHW2lGxeS5gezEQBMLTwDHf5Q= github.com/cli/browser v1.3.0 h1:LejqCrpWr+1pRqmEPDGnTZOjsMe7sehifLynZJuqJpo= github.com/cli/browser v1.3.0/go.mod h1:HH8s+fOAxjhQoBUAsKuPCbqUuxZDhQ2/aD+SzsEfBTk= -github.com/cli/go-gh/v2 v2.6.0 h1:1zXwr7mW6JDCPwXQLLtCdKnp+pNc7ZixyDeLpM1pf9I= -github.com/cli/go-gh/v2 v2.6.0/go.mod h1:h3salfqqooVpzKmHp6aUdeNx62UmxQRpLbagFSHTJGQ= +github.com/cli/go-gh/v2 v2.7.0 h1:7PfL5uBQ1e3TOj3V1qU4U/kmTSondi8Q1uyvH72ARiM= +github.com/cli/go-gh/v2 v2.7.0/go.mod h1:h3salfqqooVpzKmHp6aUdeNx62UmxQRpLbagFSHTJGQ= github.com/cli/oauth v1.0.1 h1:pXnTFl/qUegXHK531Dv0LNjW4mLx626eS42gnzfXJPA= github.com/cli/oauth v1.0.1/go.mod h1:qd/FX8ZBD6n1sVNQO3aIdRxeu5LGw9WhKnYhIIoC2A4= github.com/cli/safeexec v1.0.0/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q= From 176420907b3df2e14deffd7451ccd1764641ecbd Mon Sep 17 00:00:00 2001 From: satoqz Date: Fri, 29 Mar 2024 14:38:27 +0100 Subject: [PATCH 10/26] fix segfault in `gh repo rename` --- pkg/cmd/repo/rename/rename.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/cmd/repo/rename/rename.go b/pkg/cmd/repo/rename/rename.go index d15f2efdf..df56af951 100644 --- a/pkg/cmd/repo/rename/rename.go +++ b/pkg/cmd/repo/rename/rename.go @@ -135,7 +135,11 @@ func renameRun(opts *RenameOptions) error { remote, err := updateRemote(currRepo, newRepo, opts) if err != nil { - fmt.Fprintf(opts.IO.ErrOut, "%s Warning: unable to update remote %q: %v\n", cs.WarningIcon(), remote.Name, err) + if remote != nil { + fmt.Fprintf(opts.IO.ErrOut, "%s Warning: unable to update remote %q: %v\n", cs.WarningIcon(), remote.Name, err) + } else { + fmt.Fprintf(opts.IO.ErrOut, "%s Warning: unable to update remote: %v\n", cs.WarningIcon(), err) + } } else if opts.IO.IsStdoutTTY() { fmt.Fprintf(opts.IO.Out, "%s Updated the %q remote\n", cs.SuccessIcon(), remote.Name) } From 90b7bf97c5a9f9e40f3896c960560c123c11c6eb Mon Sep 17 00:00:00 2001 From: Meredith Lancaster Date: Mon, 1 Apr 2024 11:13:47 -0600 Subject: [PATCH 11/26] gh-attestation cmd integration (#8698) * add attestation cmd Signed-off-by: Meredith Lancaster * add codeowners Signed-off-by: Meredith Lancaster * update args passed to the attestation cmd Signed-off-by: Meredith Lancaster * rename file Signed-off-by: Meredith Lancaster * use gh-attestation branch for passing iostreams from the root Signed-off-by: Meredith Lancaster * add package security team entry to codeowners Signed-off-by: Meredith Lancaster * start moving over verify cmd and general verification code Signed-off-by: Meredith Lancaster * clean up common and verify specific policy code Signed-off-by: Meredith Lancaster * move artifact package over Signed-off-by: Meredith Lancaster * start pulling in the github api client wrapper Signed-off-by: Meredith Lancaster * fix imports Signed-off-by: Meredith Lancaster * add logger and test packages Signed-off-by: Meredith Lancaster * add additional packages to support verify command Signed-off-by: Meredith Lancaster * fix mock api client Signed-off-by: Meredith Lancaster * clean up mock api client Signed-off-by: Meredith Lancaster * include missing fields Signed-off-by: Meredith Lancaster * use correct owner Signed-off-by: Meredith Lancaster * add more mock api client options Signed-off-by: Meredith Lancaster * add download cmd Signed-off-by: Meredith Lancaster * add inspect cmd Signed-off-by: Meredith Lancaster * pass factory object to inspect cmd, add inspect sub cmd to attestation cmd Signed-off-by: Meredith Lancaster * add verify-tuf-root cmd Signed-off-by: Meredith Lancaster * pass iostream struct from command Signed-off-by: Meredith Lancaster * rename logger pkg to logger Signed-off-by: Meredith Lancaster * fix path in codeowners Signed-off-by: Meredith Lancaster * formatter Signed-off-by: Meredith Lancaster * go mod tidy Signed-off-by: Meredith Lancaster * fix printf linter issue Signed-off-by: Meredith Lancaster * fix printf linter issue Signed-off-by: Meredith Lancaster * check user's GH host for compatibility Signed-off-by: Meredith Lancaster * pass oci client to commands directly Signed-off-by: Meredith Lancaster * rename command Signed-off-by: Meredith Lancaster * mark tuf-root-verify cmd hidden Signed-off-by: Meredith Lancaster * move client initialization back to subcommands Signed-off-by: Meredith Lancaster * add more verbose options and logging Signed-off-by: Meredith Lancaster * add missing logger Signed-off-by: Meredith Lancaster * add testing around OCI and API client Signed-off-by: Meredith Lancaster * add integration test Signed-off-by: Meredith Lancaster * fix file path Signed-off-by: Meredith Lancaster * fix command Signed-off-by: Meredith Lancaster * build executable before integration test Signed-off-by: Meredith Lancaster * split integration tests Signed-off-by: Meredith Lancaster * remove integration test steps Signed-off-by: Meredith Lancaster * fix flag value Signed-off-by: Meredith Lancaster * run integration tests on ubuntu for now Signed-off-by: Meredith Lancaster * pull over doc updates Signed-off-by: Meredith Lancaster * delete unused test data Signed-off-by: Meredith Lancaster * remove Go patch version Signed-off-by: Meredith Lancaster * switch assert to require Signed-off-by: Meredith Lancaster * rename file Signed-off-by: Meredith Lancaster * move integration tests to prexisting test workflow Signed-off-by: Meredith Lancaster * use platform matrix for integration tests Signed-off-by: Meredith Lancaster * simplify build step Signed-off-by: Meredith Lancaster * use StringEnumFlag handling Signed-off-by: Meredith Lancaster * typo Signed-off-by: Meredith Lancaster * use the iostreams.Test helper func Signed-off-by: Meredith Lancaster * create interface for oci client Signed-off-by: Meredith Lancaster * add tests for oci client Signed-off-by: Meredith Lancaster * rename files Signed-off-by: Meredith Lancaster * format file Signed-off-by: Meredith Lancaster * fix shellcheck issues Signed-off-by: Meredith Lancaster * use testing TempDir method Signed-off-by: Meredith Lancaster * cleanup unused tempdir handling Signed-off-by: Meredith Lancaster * use table driven tests Signed-off-by: Meredith Lancaster * check correct cmd Signed-off-by: Meredith Lancaster * support repo option in download sub cmd Signed-off-by: Meredith Lancaster * switch over to using RunE Signed-off-by: Meredith Lancaster * unexport top level subcommand funcs Signed-off-by: Meredith Lancaster * add comment around keychain option Signed-off-by: Meredith Lancaster * update comments Signed-off-by: Meredith Lancaster * fix inconsistent naming Signed-off-by: Meredith Lancaster * add tests for CLI commands Signed-off-by: Meredith Lancaster * check for noattestationsfound err Signed-off-by: Meredith Lancaster * try out metadata abstraction instead Signed-off-by: Meredith Lancaster * switch to using MetadataStore abstraction Signed-off-by: Meredith Lancaster * include test case with failing metadata store Signed-off-by: Meredith Lancaster * look for err specific to file write Signed-off-by: Meredith Lancaster * unexport fields Signed-off-by: Meredith Lancaster * return err when an unsupported hash alg is provided Signed-off-by: Meredith Lancaster * PrintTableToStdOut returns err when rendering fails Signed-off-by: Meredith Lancaster * start adding sigstore verifier unit tests Signed-off-by: Meredith Lancaster * add more sigstore verifier specific tests Signed-off-by: Meredith Lancaster * use cli table printer Signed-off-by: Meredith Lancaster * return JSON results in slice instead of table Signed-off-by: Meredith Lancaster * move mock client to test file Signed-off-by: Meredith Lancaster * remove unneeded table printer method Signed-off-by: Meredith Lancaster * add initial tests for tufrootverify cmd Signed-off-by: Meredith Lancaster * formatting Signed-off-by: Meredith Lancaster * cleanup method Signed-off-by: Meredith Lancaster * close file in error handling branch Signed-off-by: Meredith Lancaster * normalize artifact path Signed-off-by: Meredith Lancaster * remove unneeded embedded file system Signed-off-by: Meredith Lancaster * include image name reference err Signed-off-by: Meredith Lancaster * use GH_DEBUG value for io handling Signed-off-by: Meredith Lancaster * remove quiet and verbose flags Signed-off-by: Meredith Lancaster * add more tufrootveriify tests Signed-off-by: Meredith Lancaster * GitHubTUFOptions no longer needs to return error Signed-off-by: Meredith Lancaster * remove unneeded slice Signed-off-by: Meredith Lancaster * normalize all relative paths Signed-off-by: Meredith Lancaster * clean up nil client checks Signed-off-by: Meredith Lancaster * set api server based on host Signed-off-by: Meredith Lancaster * add comment about http client Signed-off-by: Meredith Lancaster * use format flag to handle json output in verify cmd Signed-off-by: Meredith Lancaster * use format flag to handle json output Signed-off-by: Meredith Lancaster * use normalized path for cli test arg Signed-off-by: Meredith Lancaster * add tests for json output Signed-off-by: Meredith Lancaster * cleanup error wrapping Signed-off-by: Meredith Lancaster * use test fixtures correctly by normalizing path Signed-off-by: Meredith Lancaster * dont clean Signed-off-by: Meredith Lancaster * escape backwards slash for windows files with replace Signed-off-by: Meredith Lancaster * use strings.Split func Signed-off-by: Meredith Lancaster * use strings.Replace for all command tests Signed-off-by: Meredith Lancaster * use CLI cache dir to store tuf metadata Signed-off-by: Meredith Lancaster * Tweaked docstrings for gh attestation download * Tweaked docstrings for gh attestation verify * Fix for bug in gh attestation where the wrong hostname was being passed to the API client. * lets hide tuf-root-verify eh? * Forgot verify's short str. * add remote verification test Signed-off-by: Meredith Lancaster * Revert "add remote verification test" This reverts commit c0ceb99ca851d1bf25d3b276b0e49da0e04ece8c. * update json result handling Signed-off-by: Meredith Lancaster * add json tags to struct returned by command Signed-off-by: Meredith Lancaster * fix how json results are handled Signed-off-by: Meredith Lancaster * add test to ensure JSON output is valid Signed-off-by: Meredith Lancaster --------- Signed-off-by: Meredith Lancaster Co-authored-by: Phill MV --- .github/CODEOWNERS | 3 + .github/workflows/go.yml | 26 +- go.mod | 111 ++++- go.sum | 418 ++++++++++++++++-- pkg/cmd/attestation/api/attestation.go | 33 ++ pkg/cmd/attestation/api/client.go | 104 +++++ pkg/cmd/attestation/api/client_test.go | 174 ++++++++ .../attestation/api/mock_apiClient_test.go | 89 ++++ pkg/cmd/attestation/api/mock_client.go | 71 +++ pkg/cmd/attestation/artifact/artifact.go | 79 ++++ .../artifact/artifact_posix_test.go | 99 +++++ .../artifact/artifact_windows_test.go | 59 +++ pkg/cmd/attestation/artifact/digest/digest.go | 53 +++ .../artifact/digest/digest_test.go | 46 ++ pkg/cmd/attestation/artifact/file.go | 25 ++ pkg/cmd/attestation/artifact/image.go | 28 ++ pkg/cmd/attestation/artifact/image_test.go | 52 +++ pkg/cmd/attestation/artifact/oci/client.go | 70 +++ .../attestation/artifact/oci/client_test.go | 83 ++++ .../attestation/artifact/oci/mock_client.go | 34 ++ pkg/cmd/attestation/attestation.go | 28 ++ pkg/cmd/attestation/auth/host.go | 17 + pkg/cmd/attestation/download/download.go | 148 +++++++ pkg/cmd/attestation/download/download_test.go | 314 +++++++++++++ pkg/cmd/attestation/download/metadata.go | 70 +++ pkg/cmd/attestation/download/metadata_test.go | 87 ++++ pkg/cmd/attestation/download/options.go | 35 ++ pkg/cmd/attestation/download/options_test.go | 34 ++ pkg/cmd/attestation/inspect/bundle.go | 128 ++++++ pkg/cmd/attestation/inspect/bundle_test.go | 46 ++ pkg/cmd/attestation/inspect/inspect.go | 162 +++++++ pkg/cmd/attestation/inspect/inspect_test.go | 179 ++++++++ pkg/cmd/attestation/inspect/options.go | 24 + pkg/cmd/attestation/inspect/policy.go | 18 + pkg/cmd/attestation/io/handler.go | 61 +++ .../test/data/sigstore-js-2.1.0-bundle.json | 61 +++ .../test/data/sigstore-js-2.1.0.tgz | Bin 0 -> 37137 bytes .../sigstore-js-2.1.0_with_2_bundles.jsonl | 2 + .../sigstoreBundle-invalid-signature.json | 48 ++ pkg/cmd/attestation/test/path.go | 13 + .../tufrootverify/tufrootverify.go | 83 ++++ .../tufrootverify/tufrootverify_test.go | 80 ++++ .../attestation/verification/attestation.go | 115 +++++ .../verification/attestation_test.go | 49 ++ .../embed/tuf-repo.github.com/root.json | 163 +++++++ pkg/cmd/attestation/verification/policy.go | 20 + pkg/cmd/attestation/verification/sigstore.go | 208 +++++++++ .../attestation/verification/sigstore_test.go | 60 +++ pkg/cmd/attestation/verification/tuf.go | 41 ++ pkg/cmd/attestation/verification/tuf_test.go | 20 + pkg/cmd/attestation/verify/options.go | 83 ++++ pkg/cmd/attestation/verify/options_test.go | 119 +++++ pkg/cmd/attestation/verify/policy.go | 85 ++++ pkg/cmd/attestation/verify/policy_test.go | 31 ++ pkg/cmd/attestation/verify/verify.go | 216 +++++++++ pkg/cmd/attestation/verify/verify_test.go | 401 +++++++++++++++++ pkg/cmd/root/root.go | 2 + ...download-and-verify-package-attestation.sh | 38 ++ 58 files changed, 4893 insertions(+), 53 deletions(-) create mode 100644 pkg/cmd/attestation/api/attestation.go create mode 100644 pkg/cmd/attestation/api/client.go create mode 100644 pkg/cmd/attestation/api/client_test.go create mode 100644 pkg/cmd/attestation/api/mock_apiClient_test.go create mode 100644 pkg/cmd/attestation/api/mock_client.go create mode 100644 pkg/cmd/attestation/artifact/artifact.go create mode 100644 pkg/cmd/attestation/artifact/artifact_posix_test.go create mode 100644 pkg/cmd/attestation/artifact/artifact_windows_test.go create mode 100644 pkg/cmd/attestation/artifact/digest/digest.go create mode 100644 pkg/cmd/attestation/artifact/digest/digest_test.go create mode 100644 pkg/cmd/attestation/artifact/file.go create mode 100644 pkg/cmd/attestation/artifact/image.go create mode 100644 pkg/cmd/attestation/artifact/image_test.go create mode 100644 pkg/cmd/attestation/artifact/oci/client.go create mode 100644 pkg/cmd/attestation/artifact/oci/client_test.go create mode 100644 pkg/cmd/attestation/artifact/oci/mock_client.go create mode 100644 pkg/cmd/attestation/attestation.go create mode 100644 pkg/cmd/attestation/auth/host.go create mode 100644 pkg/cmd/attestation/download/download.go create mode 100644 pkg/cmd/attestation/download/download_test.go create mode 100644 pkg/cmd/attestation/download/metadata.go create mode 100644 pkg/cmd/attestation/download/metadata_test.go create mode 100644 pkg/cmd/attestation/download/options.go create mode 100644 pkg/cmd/attestation/download/options_test.go create mode 100644 pkg/cmd/attestation/inspect/bundle.go create mode 100644 pkg/cmd/attestation/inspect/bundle_test.go create mode 100644 pkg/cmd/attestation/inspect/inspect.go create mode 100644 pkg/cmd/attestation/inspect/inspect_test.go create mode 100644 pkg/cmd/attestation/inspect/options.go create mode 100644 pkg/cmd/attestation/inspect/policy.go create mode 100644 pkg/cmd/attestation/io/handler.go create mode 100644 pkg/cmd/attestation/test/data/sigstore-js-2.1.0-bundle.json create mode 100644 pkg/cmd/attestation/test/data/sigstore-js-2.1.0.tgz create mode 100644 pkg/cmd/attestation/test/data/sigstore-js-2.1.0_with_2_bundles.jsonl create mode 100644 pkg/cmd/attestation/test/data/sigstoreBundle-invalid-signature.json create mode 100644 pkg/cmd/attestation/test/path.go create mode 100644 pkg/cmd/attestation/tufrootverify/tufrootverify.go create mode 100644 pkg/cmd/attestation/tufrootverify/tufrootverify_test.go create mode 100644 pkg/cmd/attestation/verification/attestation.go create mode 100644 pkg/cmd/attestation/verification/attestation_test.go create mode 100644 pkg/cmd/attestation/verification/embed/tuf-repo.github.com/root.json create mode 100644 pkg/cmd/attestation/verification/policy.go create mode 100644 pkg/cmd/attestation/verification/sigstore.go create mode 100644 pkg/cmd/attestation/verification/sigstore_test.go create mode 100644 pkg/cmd/attestation/verification/tuf.go create mode 100644 pkg/cmd/attestation/verification/tuf_test.go create mode 100644 pkg/cmd/attestation/verify/options.go create mode 100644 pkg/cmd/attestation/verify/options_test.go create mode 100644 pkg/cmd/attestation/verify/policy.go create mode 100644 pkg/cmd/attestation/verify/policy_test.go create mode 100644 pkg/cmd/attestation/verify/verify.go create mode 100644 pkg/cmd/attestation/verify/verify_test.go create mode 100755 test/integration/attestation-cmd/download-and-verify-package-attestation.sh diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index cd5695abc..5aad3e925 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -2,3 +2,6 @@ pkg/cmd/codespace/ @cli/codespaces internal/codespaces/ @cli/codespaces + +# Limit Package Security team ownership to the attestation command package +pkg/cmd/attestation/ @cli/package-security diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 13b598d52..7284a9e08 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -1,4 +1,4 @@ -name: Tests +name: Unit and Integration Tests on: [push, pull_request] permissions: @@ -37,3 +37,27 @@ jobs: - name: Build run: go build -v ./cmd/gh + + integration-tests: + env: + GH_TOKEN: ${{ github.token }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + runs-on: ${{ matrix.os }} + + steps: + - name: Set up Go 1.21 + uses: actions/setup-go@v5 + with: + go-version: 1.21 + + - name: Check out code + uses: actions/checkout@v4 + + - name: Build executable + run: make + + - name: Run attestation command integration Tests + run: ./test/integration/attestation-cmd/download-and-verify-package-attestation.sh diff --git a/go.mod b/go.mod index 3d432895c..e419c3224 100644 --- a/go.mod +++ b/go.mod @@ -14,14 +14,17 @@ require ( github.com/cli/safeexec v1.0.1 github.com/cpuguy83/go-md2man/v2 v2.0.4 github.com/creack/pty v1.1.21 + github.com/distribution/reference v0.5.0 github.com/gabriel-vasile/mimetype v1.4.3 github.com/gdamore/tcell/v2 v2.5.4 - github.com/google/go-cmp v0.5.9 + github.com/google/go-cmp v0.6.0 + github.com/google/go-containerregistry v0.19.0 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 - github.com/gorilla/websocket v1.4.2 + github.com/gorilla/websocket v1.5.0 github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-version v1.3.0 github.com/henvic/httpretty v0.1.3 + github.com/in-toto/in-toto-golang v0.9.0 github.com/joho/godotenv v1.5.1 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 github.com/mattn/go-colorable v0.1.13 @@ -29,18 +32,20 @@ require ( github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d github.com/microsoft/dev-tunnels v0.0.25 github.com/muhammadmuzzammil1998/jsonc v0.0.0-20201229145248-615b0916ca38 - github.com/opentracing/opentracing-go v1.1.0 + github.com/opentracing/opentracing-go v1.2.0 github.com/rivo/tview v0.0.0-20221029100920-c4a7e501810d github.com/shurcooL/githubv4 v0.0.0-20230704064427-599ae7bbf278 - github.com/spf13/cobra v1.6.1 + github.com/sigstore/protobuf-specs v0.3.0 + github.com/sigstore/sigstore-go v0.2.1-0.20240222221148-8bd2a8139edc + github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.4 github.com/zalando/go-keyring v0.2.4 - golang.org/x/crypto v0.17.0 - golang.org/x/sync v0.1.0 - golang.org/x/term v0.15.0 + golang.org/x/crypto v0.19.0 + golang.org/x/sync v0.6.0 + golang.org/x/term v0.17.0 golang.org/x/text v0.14.0 - google.golang.org/grpc v1.56.3 + google.golang.org/grpc v1.61.0 google.golang.org/protobuf v1.33.0 gopkg.in/h2non/gock.v1 v1.1.2 gopkg.in/yaml.v3 v3.0.1 @@ -49,41 +54,113 @@ require ( require ( github.com/alecthomas/chroma v0.10.0 // indirect github.com/alessio/shellescape v1.4.2 // indirect + github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/aymanbagabas/go-osc52 v1.0.3 // indirect github.com/aymerick/douceur v0.2.0 // indirect + github.com/blang/semver v3.5.1+incompatible // indirect github.com/cli/browser v1.3.0 // indirect github.com/cli/shurcooL-graphql v0.0.4 // indirect + github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect + github.com/cyberphone/json-canonicalization v0.0.0-20220623050100-57a0ce2678a7 // indirect github.com/danieljoos/wincred v1.2.1 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352 // indirect + github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7 // indirect github.com/dlclark/regexp2 v1.4.0 // indirect - github.com/fatih/color v1.7.0 // indirect + github.com/docker/cli v24.0.0+incompatible // indirect + github.com/docker/distribution v2.8.2+incompatible // indirect + github.com/docker/docker v24.0.7+incompatible // indirect + github.com/docker/docker-credential-helpers v0.7.0 // indirect + github.com/fatih/color v1.14.1 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/gdamore/encoding v1.0.0 // indirect + github.com/go-chi/chi v4.1.2+incompatible // indirect + github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-openapi/analysis v0.22.0 // indirect + github.com/go-openapi/errors v0.21.0 // indirect + github.com/go-openapi/jsonpointer v0.20.2 // indirect + github.com/go-openapi/jsonreference v0.20.4 // indirect + github.com/go-openapi/loads v0.21.5 // indirect + github.com/go-openapi/runtime v0.27.1 // indirect + github.com/go-openapi/spec v0.20.14 // indirect + github.com/go-openapi/strfmt v0.22.0 // indirect + github.com/go-openapi/swag v0.22.9 // indirect + github.com/go-openapi/validate v0.22.6 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/golang/protobuf v1.5.3 // indirect + github.com/google/certificate-transparency-go v1.1.7 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/gorilla/css v1.0.0 // indirect github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect - github.com/hashicorp/errwrap v1.0.0 // indirect - github.com/inconshreveable/mousetrap v1.0.1 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-retryablehttp v0.7.5 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/itchyny/gojq v0.12.13 // indirect github.com/itchyny/timefmt-go v0.1.5 // indirect - github.com/kr/text v0.2.0 // indirect + github.com/jedisct1/go-minisign v0.0.0-20211028175153-1c139d1cc84b // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/klauspost/compress v1.17.0 // indirect + github.com/letsencrypt/boulder v0.0.0-20230907030200-6d76a0f91e1e // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect github.com/microcosm-cc/bluemonday v1.0.26 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/muesli/reflow v0.3.0 // indirect github.com/muesli/termenv v0.13.0 // indirect + github.com/oklog/ulid v1.3.1 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0-rc5 // indirect + github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rivo/uniseg v0.4.4 // indirect github.com/rodaine/table v1.0.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sassoftware/relic v7.2.1+incompatible // indirect + github.com/secure-systems-lab/go-securesystemslib v0.8.0 // indirect + github.com/shibumi/go-pathspec v1.3.0 // indirect github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 // indirect + github.com/sigstore/rekor v1.3.5 // indirect + github.com/sigstore/sigstore v1.8.1 // indirect + github.com/sigstore/timestamp-authority v1.2.2 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/viper v1.18.2 // indirect github.com/stretchr/objx v0.5.0 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/theupdateframework/go-tuf v0.7.0 // indirect + github.com/theupdateframework/go-tuf/v2 v2.0.0-20240222081530-454b12158917 // indirect github.com/thlib/go-timezone-local v0.0.0-20210907160436-ef149e42d28e // indirect + github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect + github.com/transparency-dev/merkle v0.0.2 // indirect + github.com/vbatts/tar-split v0.11.3 // indirect github.com/yuin/goldmark v1.5.2 // indirect github.com/yuin/goldmark-emoji v1.0.1 // indirect - golang.org/x/net v0.17.0 // indirect + go.mongodb.org/mongo-driver v1.13.1 // indirect + go.opentelemetry.io/otel v1.22.0 // indirect + go.opentelemetry.io/otel/metric v1.22.0 // indirect + go.opentelemetry.io/otel/trace v1.22.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.26.0 // indirect + golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect + golang.org/x/mod v0.15.0 // indirect + golang.org/x/net v0.21.0 // indirect golang.org/x/sys v0.18.0 // indirect - google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect - gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect + google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac // indirect + gopkg.in/go-jose/go-jose.v2 v2.6.1 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + k8s.io/klog/v2 v2.120.0 // indirect ) diff --git a/go.sum b/go.sum index c05d673cd..ac71cdba3 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,31 @@ +cloud.google.com/go v0.112.0 h1:tpFCD7hpHFlQ8yPwT3x+QeXqc2T6+n6T+hmABHfDUSM= +cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= +cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI= +cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8= +cloud.google.com/go/kms v1.15.5 h1:pj1sRfut2eRbD9pFRjNnPNg/CzJPuQAzUujMIM1vVeM= +cloud.google.com/go/kms v1.15.5/go.mod h1:cU2H5jnp6G2TDpUGZyqTCoy1n16fbubHZjmVXSMtwDI= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/AdamKorcz/go-fuzz-headers-1 v0.0.0-20230919221257-8b5d3ce2d11d h1:zjqpY4C7H15HjRPEenkS4SAn3Jy2eRRjkjZbGR30TOg= +github.com/AdamKorcz/go-fuzz-headers-1 v0.0.0-20230919221257-8b5d3ce2d11d/go.mod h1:XNqJ7hv2kY++g8XEHREpi+JqZo3+0l+CH2egBVN4yqM= github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1 h1:lGlwhPtrX6EVml1hO0ivjkUxsSyl4dsiw9qcA1k/3IQ= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1/go.mod h1:RKUqNu35KJYcVG/fqTRqmuXJZYNhYkBrnC/hX7yGbTA= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1 h1:sO0/P7g68FrryJzljemN+6GTssUXdANk6aJ7T1ZxnsQ= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1/go.mod h1:h8hyGFDsU5HMivxiS2iYFZsgDbU9OnnJ163x5UGVKYo= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1 h1:6oNBlSdi1QqM1PNW7FPA6xOGA5UNsXnkaYZz9vdPGhA= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1/go.mod h1:s4kgfzA0covAXNicZHDMN58jExvcng2mC/DepXiF1EI= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1 h1:MyVTgWR8qd/Jw1Le0NZebGBUCLbtak3bJ3z1OlqZBpw= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1/go.mod h1:GpPjLhVR9dnUoJMyHWSPy71xY9/lcmpzIPZXmF0FCVY= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 h1:D3occbWoio4EBLkbkevetNMAVX197GkzbUMtqjGWn80= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0/go.mod h1:bTSOgj05NGRuHHhQwAdPnYr9TOdNmKlZTgGLL6nyAdI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1 h1:DzHpqpoJVaCgOUdVHxE8QB52S6NiVdDQvGlny1qvPqA= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= @@ -8,14 +34,54 @@ github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbf github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= github.com/alessio/shellescape v1.4.2 h1:MHPfaU+ddJ0/bYWpgIeUnQUqKrlJ1S7BfEYPM4uEoM0= github.com/alessio/shellescape v1.4.2/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/aws/aws-sdk-go v1.49.21 h1:Rl8KW6HqkwzhATwvXhyr7vD4JFUMi7oXGAw9SrxxIFY= +github.com/aws/aws-sdk-go v1.49.21/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go-v2 v1.24.1 h1:xAojnj+ktS95YZlDf0zxWBkbFtymPeDP+rvUQIH3uAU= +github.com/aws/aws-sdk-go-v2 v1.24.1/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4= +github.com/aws/aws-sdk-go-v2/config v1.26.6 h1:Z/7w9bUqlRI0FFQpetVuFYEsjzE3h7fpU6HuGmfPL/o= +github.com/aws/aws-sdk-go-v2/config v1.26.6/go.mod h1:uKU6cnDmYCvJ+pxO9S4cWDb2yWWIH5hra+32hVh1MI4= +github.com/aws/aws-sdk-go-v2/credentials v1.16.16 h1:8q6Rliyv0aUFAVtzaldUEcS+T5gbadPbWdV1WcAddK8= +github.com/aws/aws-sdk-go-v2/credentials v1.16.16/go.mod h1:UHVZrdUsv63hPXFo1H7c5fEneoVo9UXiz36QG1GEPi0= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 h1:c5I5iH+DZcH3xOIMlz3/tCKJDaHFwYEmxvlh2fAcFo8= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11/go.mod h1:cRrYDYAMUohBJUtUnOhydaMHtiK/1NZ0Otc9lIb6O0Y= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 h1:vF+Zgd9s+H4vOXd5BMaPWykta2a6Ih0AKLq/X6NYKn4= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10/go.mod h1:6BkRjejp/GR4411UGqkX8+wFMbFbqsUIimfK4XjOKR4= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 h1:nYPe006ktcqUji8S2mqXf9c/7NdiKriOwMvWQHgYztw= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10/go.mod h1:6UV4SZkVvmODfXKql4LCbaZUpF7HO2BX38FgBf9ZOLw= +github.com/aws/aws-sdk-go-v2/internal/ini v1.7.3 h1:n3GDfwqF2tzEkXlv5cuy4iy7LpKDtqDMcNLfZDu9rls= +github.com/aws/aws-sdk-go-v2/internal/ini v1.7.3/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 h1:/b31bi3YVNlkzkBrm9LfpaKoaYZUxIAj4sHfOTmLfqw= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4/go.mod h1:2aGXHFmbInwgP9ZfpmdIfOELL79zhdNYNmReK8qDfdQ= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 h1:DBYTXwIGQSGs9w4jKm60F5dmCQ3EEruxdc0MFh+3EY4= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10/go.mod h1:wohMUQiFdzo0NtxbBg0mSRGZ4vL3n0dKjLTINdcIino= +github.com/aws/aws-sdk-go-v2/service/kms v1.27.9 h1:W9PbZAZAEcelhhjb7KuwUtf+Lbc+i7ByYJRuWLlnxyQ= +github.com/aws/aws-sdk-go-v2/service/kms v1.27.9/go.mod h1:2tFmR7fQnOdQlM2ZCEPpFnBIQD1U8wmXmduBgZbOag0= +github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 h1:eajuO3nykDPdYicLlP3AGgOyVN3MOlFmZv7WGTuJPow= +github.com/aws/aws-sdk-go-v2/service/sso v1.18.7/go.mod h1:+mJNDdF+qiUlNKNC3fxn74WWNN+sOiGOEImje+3ScPM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 h1:QPMJf+Jw8E1l7zqhZmMlFw6w1NmfkfiSK8mS4zOx3BA= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7/go.mod h1:ykf3COxYI0UJmxcfcxcVuz7b6uADi1FkiUz6Eb7AgM8= +github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 h1:NzO4Vrau795RkUdSHKEwiR01FaGzGOH1EETJ+5QHnm0= +github.com/aws/aws-sdk-go-v2/service/sts v1.26.7/go.mod h1:6h2YuIoxaMSCFf5fi1EgZAwdfkGMgDY+DVfa61uLe4U= +github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM= +github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE= github.com/aymanbagabas/go-osc52 v1.0.3 h1:DTwqENW7X9arYimJrPeGZcV0ln14sGMt3pHZspWD+Mg= github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= 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/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/briandowns/spinner v1.18.1 h1:yhQmQtM1zsqFsouh09Bk/jCjd50pC3EOGsh28gLVvwY= github.com/briandowns/spinner v1.18.1/go.mod h1:mQak9GHqbspjC/5iUx3qMlIho8xBS/ppAL/hX5SmPJU= +github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M= +github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/charmbracelet/glamour v0.6.0 h1:wi8fse3Y7nfcabbbDuwolqTqMQPMnVPeZhDM273bISc= github.com/charmbracelet/glamour v0.6.0/go.mod h1:taqWV4swIMMbWALc0m7AfE9JkPSU8om2538k9ITBxOc= github.com/charmbracelet/lipgloss v0.5.0 h1:lulQHuVeodSgDez+3rGiuxlPVXSnhth442DATR2/8t8= @@ -32,70 +98,199 @@ github.com/cli/safeexec v1.0.1 h1:e/C79PbXF4yYTN/wauC4tviMxEV13BwljGj0N9j+N00= github.com/cli/safeexec v1.0.1/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q= github.com/cli/shurcooL-graphql v0.0.4 h1:6MogPnQJLjKkaXPyGqPRXOI2qCsQdqNfUY1QSJu2GuY= github.com/cli/shurcooL-graphql v0.0.4/go.mod h1:3waN4u02FiZivIV+p1y4d0Jo1jc6BViMA73C+sZo2fk= +github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= +github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4= +github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSkDwbkEETK84kQgEeFwDC+62k= +github.com/containerd/stargz-snapshotter/estargz v0.14.3/go.mod h1:KY//uOCIkSuNAHhJogcZtrNHdKrA99/FCCRjE3HD36o= 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/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0= github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/cyberphone/json-canonicalization v0.0.0-20220623050100-57a0ce2678a7 h1:vU+EP9ZuFUCYE0NYLwTSob+3LNEJATzNfP/DC7SWGWI= +github.com/cyberphone/json-canonicalization v0.0.0-20220623050100-57a0ce2678a7/go.mod h1:uzvlm1mxhHkdfqitSA92i7Se+S9ksOn3a3qmv/kyOCw= github.com/danieljoos/wincred v1.2.1 h1:dl9cBrupW8+r5250DYkYxocLeZ1Y4vB1kxgtjxw8GQs= github.com/danieljoos/wincred v1.2.1/go.mod h1:uGaFL9fDn3OLTvzCGulzE+SzjEe5NGlh5FdCcyfPwps= 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= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/digitorus/pkcs7 v0.0.0-20230713084857-e76b763bdc49/go.mod h1:SKVExuS+vpu2l9IoOc0RwqE7NYnb0JlcFHFnEJkVDzc= +github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352 h1:ge14PCmCvPjpMQMIAH7uKg0lrtNSOdpYsRXlwk3QbaE= +github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352/go.mod h1:SKVExuS+vpu2l9IoOc0RwqE7NYnb0JlcFHFnEJkVDzc= +github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7 h1:lxmTCgmHE1GUYL7P0MlNa00M67axePTq+9nBSGddR8I= +github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7/go.mod h1:GvWntX9qiTlOud0WkQ6ewFm0LPy5JUR1Xo0Ngbd1w6Y= +github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= +github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E= github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= -github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= +github.com/docker/cli v24.0.0+incompatible h1:0+1VshNwBQzQAx9lOl+OYCTCEAD8fKs/qeXMx3O0wqM= +github.com/docker/cli v24.0.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= +github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM= +github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= +github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= +github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= github.com/gdamore/tcell/v2 v2.5.4 h1:TGU4tSjD3sCL788vFNeJnTdzpNKIw1H5dgLnJRQVv/k= github.com/gdamore/tcell/v2 v2.5.4/go.mod h1:dZgRy5v4iMobMEcWNYBtREnDZAT9DYmfqIkrgEMxLyw= +github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= +github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA= +github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-openapi/analysis v0.22.0 h1:wQ/d07nf78HNj4u+KiSY0sT234IAyePPbMgpUjUJQR0= +github.com/go-openapi/analysis v0.22.0/go.mod h1:acDnkkCI2QxIo8sSIPgmp1wUlRohV7vfGtAIVae73b0= +github.com/go-openapi/errors v0.21.0 h1:FhChC/duCnfoLj1gZ0BgaBmzhJC2SL/sJr8a2vAobSY= +github.com/go-openapi/errors v0.21.0/go.mod h1:jxNTMUxRCKj65yb/okJGEtahVd7uvWnuWfj53bse4ho= +github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q= +github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs= +github.com/go-openapi/jsonreference v0.20.4 h1:bKlDxQxQJgwpUSgOENiMPzCTBVuc7vTdXSSgNeAhojU= +github.com/go-openapi/jsonreference v0.20.4/go.mod h1:5pZJyJP2MnYCpoeoMAql78cCHauHj0V9Lhc506VOpw4= +github.com/go-openapi/loads v0.21.5 h1:jDzF4dSoHw6ZFADCGltDb2lE4F6De7aWSpe+IcsRzT0= +github.com/go-openapi/loads v0.21.5/go.mod h1:PxTsnFBoBe+z89riT+wYt3prmSBP6GDAQh2l9H1Flz8= +github.com/go-openapi/runtime v0.27.1 h1:ae53yaOoh+fx/X5Eaq8cRmavHgDma65XPZuvBqvJYto= +github.com/go-openapi/runtime v0.27.1/go.mod h1:fijeJEiEclyS8BRurYE1DE5TLb9/KZl6eAdbzjsrlLU= +github.com/go-openapi/spec v0.20.14 h1:7CBlRnw+mtjFGlPDRZmAMnq35cRzI91xj03HVyUi/Do= +github.com/go-openapi/spec v0.20.14/go.mod h1:8EOhTpBoFiask8rrgwbLC3zmJfz4zsCUueRuPM6GNkw= +github.com/go-openapi/strfmt v0.22.0 h1:Ew9PnEYc246TwrEspvBdDHS4BVKXy/AOVsfqGDgAcaI= +github.com/go-openapi/strfmt v0.22.0/go.mod h1:HzJ9kokGIju3/K6ap8jL+OlGAbjpSv27135Yr9OivU4= +github.com/go-openapi/swag v0.22.9 h1:XX2DssF+mQKM2DHsbgZK74y/zj4mo9I99+89xUmuZCE= +github.com/go-openapi/swag v0.22.9/go.mod h1:3/OXnFfnMAwBD099SwYRk7GD3xOrr1iL7d/XNLXVVwE= +github.com/go-openapi/validate v0.22.6 h1:+NhuwcEYpWdO5Nm4bmvhGLW0rt1Fcc532Mu3wpypXfo= +github.com/go-openapi/validate v0.22.6/go.mod h1:eaddXSqKeTg5XpSmj1dYyFTK/95n/XHwcOY+BMxKMyM= +github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= +github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw= +github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/certificate-transparency-go v1.1.7 h1:IASD+NtgSTJLPdzkthwvAG1ZVbF2WtFg4IvoA68XGSw= +github.com/google/certificate-transparency-go v1.1.7/go.mod h1:FSSBo8fyMVgqptbfF6j5p/XNdgQftAhSmXcIxV9iphE= +github.com/google/go-cmp v0.5.2/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.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.19.0 h1:uIsMRBV7m/HDkDxE/nXMnv1q+lOOSPlQ/ywc5JbB8Ic= +github.com/google/go-containerregistry v0.19.0/go.mod h1:u0qB2l7mvtWVR5kNcbFIhFY1hLbf8eeGapA+vbFDCtQ= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/google/tink/go v1.7.0 h1:6Eox8zONGebBFcCBqkVmt60LaWZa6xg1cl/DwAh/J1w= +github.com/google/tink/go v1.7.0/go.mod h1:GAUOd+QE3pgj9q8VKIGTCP33c/B7eb4NhxLcgTJZStM= +github.com/google/trillian v1.6.0 h1:jMBeDBIkINFvS2n6oV5maDqfRlxREAc6CW9QYWQ0qT4= +github.com/google/trillian v1.6.0/go.mod h1:Yu3nIMITzNhhMJEHjAtp6xKiu+H/iHu2Oq5FjV2mCWI= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= +github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= +github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= +github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= -github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= -github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= +github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M= +github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= +github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7 h1:UpiO20jno/eV1eVZcxqWnUohyKRe1g8FPV/xH1s/2qs= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= +github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= +github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/go-version v1.3.0 h1:McDWVJIU/y+u1BRV06dPaLfLCaT7fUTJLp5r04x7iNw= github.com/hashicorp/go-version v1.3.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/vault/api v1.10.0 h1:/US7sIjWN6Imp4o/Rj1Ce2Nr5bki/AXi9vAW3p2tOJQ= +github.com/hashicorp/vault/api v1.10.0/go.mod h1:jo5Y/ET+hNyz+JnKDt8XLAdKs+AM0G5W0Vp1IrFI8N8= github.com/henvic/httpretty v0.1.3 h1:4A6vigjz6Q/+yAfTD4wqipCv+Px69C7Th/NhT0ApuU8= github.com/henvic/httpretty v0.1.3/go.mod h1:UUEv7c2kHZ5SPQ51uS3wBpzPDibg2U3Y+IaXyHy5GBg= github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog= github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= -github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= -github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef h1:A9HsByNhogrvm9cWb28sjiS3i7tcKCkflWFEkHfuAgM= +github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs= +github.com/in-toto/in-toto-golang v0.9.0 h1:tHny7ac4KgtsfrG6ybU8gVOZux2H8jN05AXJ9EBM1XU= +github.com/in-toto/in-toto-golang v0.9.0/go.mod h1:xsBVrVsHNsB61++S6Dy2vWosKhuA3lUTQd+eF9HdeMo= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/itchyny/gojq v0.12.13 h1:IxyYlHYIlspQHHTE0f3cJF0NKDMfajxViuhBLnHd/QU= github.com/itchyny/gojq v0.12.13/go.mod h1:JzwzAqenfhrPUuwbmEz3nu3JQmFLlQTQMUcOdnu/Sf4= github.com/itchyny/timefmt-go v0.1.5 h1:G0INE2la8S6ru/ZI5JecgyzbbJNs5lG1RcBqa7Jm6GE= github.com/itchyny/timefmt-go v0.1.5/go.mod h1:nEP7L+2YmAbT2kZ2HfSs1d8Xtw9LY8D2stDBckWakZ8= +github.com/jedisct1/go-minisign v0.0.0-20211028175153-1c139d1cc84b h1:ZGiXF8sz7PDk6RgkP+A/SFfUD0ZR/AgG6SpRNEDKZy8= +github.com/jedisct1/go-minisign v0.0.0-20211028175153-1c139d1cc84b/go.mod h1:hQmNrgofl+IY/8L+n20H6E6PWBBTokdsv+q49j0QhsU= +github.com/jellydator/ttlcache/v3 v3.1.1 h1:RCgYJqo3jgvhl+fEWvjNW8thxGWsgxi+TPhRir1Y9y8= +github.com/jellydator/ttlcache/v3 v3.1.1/go.mod h1:hi7MGFdMAwZna5n2tuvh63DvFLzVKySzCVW6+0gA2n4= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmhodges/clock v1.2.0 h1:eq4kys+NI0PLngzaHEe7AmPT90XMGIEySD1JfV1PDIs= +github.com/jmhodges/clock v1.2.0/go.mod h1:qKjhA7x7u/lQpPB1XAqX1b1lCI/w3/fNuYpI/ZjLynI= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 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/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= +github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/letsencrypt/boulder v0.0.0-20230907030200-6d76a0f91e1e h1:RLTpX495BXToqxpM90Ws4hXEo4Wfh81jr9DX1n/4WOo= +github.com/letsencrypt/boulder v0.0.0-20230907030200-6d76a0f91e1e/go.mod h1:EAuqr9VFWxBi9nD5jc/EA2MT1RFty9288TF6zdtYoCU= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= @@ -110,6 +305,9 @@ github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRC github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= @@ -118,6 +316,11 @@ github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3r github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs= github.com/microsoft/dev-tunnels v0.0.25 h1:UlMKUI+2O8cSu4RlB52ioSyn1LthYSVkJA+CSTsdKoA= github.com/microsoft/dev-tunnels v0.0.25/go.mod h1:frU++12T/oqxckXkDpTuYa427ncguEOodSPZcGCCrzQ= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= @@ -128,12 +331,33 @@ github.com/muhammadmuzzammil1998/jsonc v0.0.0-20201229145248-615b0916ca38 h1:0Fr github.com/muhammadmuzzammil1998/jsonc v0.0.0-20201229145248-615b0916ca38/go.mod h1:saF2fIVw4banK0H4+/EuqfFLpRnoy5S+ECwTOCcRcSU= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= +github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= -github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= +github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= +github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= +github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= +github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= +github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/rivo/tview v0.0.0-20221029100920-c4a7e501810d h1:jKIUJdMcIVGOSHi6LSqJqw9RqblyblE2ZrHvFbWR3S0= github.com/rivo/tview v0.0.0-20221029100920-c4a7e501810d/go.mod h1:YX2wUZOcJGOIycErz2s9KvDaP0jnWwRCirQMPLPpQ+Y= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= @@ -142,28 +366,95 @@ github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rodaine/table v1.0.1 h1:U/VwCnUxlVYxw8+NJiLIuCxA/xa6jL38MY3FYysVWWQ= github.com/rodaine/table v1.0.1/go.mod h1:UVEtfBsflpeEcD56nF4F5AocNFta0ZuolpSVdPtlmP4= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= +github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sassoftware/relic v7.2.1+incompatible h1:Pwyh1F3I0r4clFJXkSI8bOyJINGqpgjJU3DYAZeI05A= +github.com/sassoftware/relic v7.2.1+incompatible/go.mod h1:CWfAxv73/iLZ17rbyhIEq3K9hs5w6FpNMdUT//qR+zk= +github.com/sassoftware/relic/v7 v7.6.1 h1:O5s8ewCgq5QYNpv45dK4u6IpBmDM9RIcsbf/G1uXepQ= +github.com/sassoftware/relic/v7 v7.6.1/go.mod h1:NxwtWxWxlUa9as2qZi635Ye6bBT/tGnMALLq7dSfOOU= +github.com/secure-systems-lab/go-securesystemslib v0.8.0 h1:mr5An6X45Kb2nddcFlbmfHkLguCE9laoZCUzEEpIZXA= +github.com/secure-systems-lab/go-securesystemslib v0.8.0/go.mod h1:UH2VZVuJfCYR8WgMlCU1uFsOUU+KeyrTWcSS73NBOzU= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI= +github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh5tVaaMCl3jE= github.com/shurcooL/githubv4 v0.0.0-20230704064427-599ae7bbf278 h1:kdEGVAV4sO46DPtb8k793jiecUEhaX9ixoIBt41HEGU= github.com/shurcooL/githubv4 v0.0.0-20230704064427-599ae7bbf278/go.mod h1:zqMwyHmnN/eDOZOdiTohqIUKUrTFX62PNlu7IJdu0q8= github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 h1:17JxqqJY66GmZVHkmAsGEkcIu0oCe3AM420QDgGwZx0= github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466/go.mod h1:9dIRpgIY7hVhoqfe0/FcYp0bpInZaT7dc3BYOprrIUE= -github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= -github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= +github.com/sigstore/protobuf-specs v0.3.0 h1:E49qS++llp4psM+3NNVEb+C4AD422bT9VkOQIPrNLpA= +github.com/sigstore/protobuf-specs v0.3.0/go.mod h1:ynKzXpqr3dUj2Xk9O/5ZUhjnpi0F53DNi5AdH6pS3jc= +github.com/sigstore/rekor v1.3.5 h1:QoVXcS7NppKY+rpbEFVHr4evGDZBBSh65X0g8PXoUkQ= +github.com/sigstore/rekor v1.3.5/go.mod h1:CWqOk/fmnPwORQmm7SyDgB54GTJizqobbZ7yOP1lvw8= +github.com/sigstore/sigstore v1.8.1 h1:mAVposMb14oplk2h/bayPmIVdzbq2IhCgy4g6R0ZSjo= +github.com/sigstore/sigstore v1.8.1/go.mod h1:02SL1158BSj15bZyOFz7m+/nJzLZfFd9A8ab3Kz7w/E= +github.com/sigstore/sigstore-go v0.2.1-0.20240222221148-8bd2a8139edc h1:S8mCkKxbnn38DQz41jnPyaSKZYviGT7wMLM+7iHOp3I= +github.com/sigstore/sigstore-go v0.2.1-0.20240222221148-8bd2a8139edc/go.mod h1:yODm8pZ33BSpmezTcvvwzgHZgDxbqB6hBAg1izfG+EQ= +github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.1 h1:rEDdUefulkIQaMJyzLwtgPDLNXBIltBABiFYfb0YmgQ= +github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.1/go.mod h1:RCdYCc1IxCYWzh2IdzdA6Yf7JIY0cMRqH08fpQYechw= +github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.1 h1:DvRWG99QGWZC5mp42SEde2Xke/Q384Idnj2da7yB+Mk= +github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.1/go.mod h1:s13mo3a0UCQS3+PAUUZfvKe48sMDMsHk2GE1b2YfPcU= +github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.1 h1:lwdRsJv1UbBemuk7w5YfXAQilQxMoFevrzamdPbG0wY= +github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.1/go.mod h1:2OaSQ80EcdyVRSQ3T4d1lsc6Scopblsiq8U2AEk5K1A= +github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.8.1 h1:9Ki0qudKpc1FQdef7xHO2bkLyTuw+qNUpWRzjBEmF4c= +github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.8.1/go.mod h1:nhIgyu4YwwNgalIwTGsoAzam16jjAn3ADRSWKbWPwGI= +github.com/sigstore/timestamp-authority v1.2.2 h1:X4qyutnCQqJ0apMewFyx+3t7Tws00JQ/JonBiu3QvLE= +github.com/sigstore/timestamp-authority v1.2.2/go.mod h1:nEah4Eq4wpliDjlY342rXclGSO7Kb9hoRrl9tqLW13A= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= +github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/theupdateframework/go-tuf v0.7.0 h1:CqbQFrWo1ae3/I0UCblSbczevCCbS31Qvs5LdxRWqRI= +github.com/theupdateframework/go-tuf v0.7.0/go.mod h1:uEB7WSY+7ZIugK6R1hiBMBjQftaFzn7ZCDJcp1tCUug= +github.com/theupdateframework/go-tuf/v2 v2.0.0-20240222081530-454b12158917 h1:Ov8+IAeR7pivNDC0Cd25MyyaCR3WPlGBED4wNxIFQ8s= +github.com/theupdateframework/go-tuf/v2 v2.0.0-20240222081530-454b12158917/go.mod h1:+gWwqe1pk4nvGeOKosGJqPgD+N/kbD9M0QVLL9TGIYU= github.com/thlib/go-timezone-local v0.0.0-20210907160436-ef149e42d28e h1:BuzhfgfWQbX0dWzYzT1zsORLnHRv3bcRcsaUk0VmXA8= github.com/thlib/go-timezone-local v0.0.0-20210907160436-ef149e42d28e/go.mod h1:/Tnicc6m/lsJE0irFMA0LfIwTBo4QP7A8IfyIv4zZKI= +github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 h1:e/5i7d4oYZ+C1wj2THlRK+oAhjeS/TRQwMfkIuet3w0= +github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399/go.mod h1:LdwHTNJT99C5fTAzDz0ud328OgXz+gierycbcIx2fRs= +github.com/transparency-dev/merkle v0.0.2 h1:Q9nBoQcZcgPamMkGn7ghV8XiTZ/kRxn1yCG81+twTK4= +github.com/transparency-dev/merkle v0.0.2/go.mod h1:pqSy+OXefQ1EDUVmAJ8MUhHB9TXGuzVAT58PqBoHz1A= +github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8= +github.com/vbatts/tar-split v0.11.3 h1:hLFqsOLQ1SsppQNTMpkpPXClLDfC2A3Zgy9OUU+RVck= +github.com/vbatts/tar-split v0.11.3/go.mod h1:9QlHN18E+fEH7RdG+QAJJcuya3rqT7eXSTY7wGrAokY= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= +github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.5.2 h1:ALmeCk/px5FSm1MAcFBAsVKZjDuMVj8Tm7FFIlMJnqU= @@ -172,47 +463,85 @@ github.com/yuin/goldmark-emoji v1.0.1 h1:ctuWEyzGBwiucEqxzwe0SOYDXPAucOrE9NQC18W github.com/yuin/goldmark-emoji v1.0.1/go.mod h1:2w1E6FEWLcDQkoTE+7HU6QF1F6SLlNGjRIBbIZQFqkQ= github.com/zalando/go-keyring v0.2.4 h1:wi2xxTqdiwMKbM6TWwi+uJCG/Tum2UV0jqaQhCa9/68= github.com/zalando/go-keyring v0.2.4/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk= +go.mongodb.org/mongo-driver v1.13.1 h1:YIc7HTYsKndGK4RFzJ3covLz1byri52x0IoMB0Pt/vk= +go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwDtFu/Z9+eo= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 h1:UNQQKPfTDe1J81ViolILjTKPr9WetKW6uei2hFgJmFs= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0/go.mod h1:r9vWsPS/3AQItv3OSlEJ/E4mbrhUbbw18meOjArPtKQ= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 h1:sv9kVfal0MK0wBMCOGr+HeJm9v803BkJxGrk2au7j08= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0/go.mod h1:SK2UL73Zy1quvRPonmOmRDiWk1KBV3LyIeeIxcEApWw= +go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y= +go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI= +go.opentelemetry.io/otel/metric v1.22.0 h1:lypMQnGyJYeuYPhOM/bgjbFM6WE44W1/T45er4d8Hhg= +go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY= +go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= +go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= +go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0= +go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo= +go.step.sm/crypto v0.43.0 h1:siTS/iiqaX4qBUeTxVyag5I2rijuKOMDkXSnrKcei7s= +go.step.sm/crypto v0.43.0/go.mod h1:iKrtuRbFlqimEG/+fWSu7kcZzl4Bd/+w5xkuqA5OSic= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= 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.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= +golang.org/x/mod v0.15.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-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g= -golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= +golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= 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 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.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-20190222072716-a9d3bda3a223/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-20210423082822-04245dca01da/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-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/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-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220906165534-d0df966e6959/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= -golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= 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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.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= @@ -220,21 +549,40 @@ golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= 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= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= -google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= -google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc= -google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= +google.golang.org/api v0.160.0 h1:SEspjXHVqE1m5a1fRy8JFB+5jSu+V0GEDKDghF3ttO4= +google.golang.org/api v0.160.0/go.mod h1:0mu0TpK33qnydLvWqbImq2b1eQ5FHRSDCBzAxX9ZHyw= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= +google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac h1:ZL/Teoy/ZGnzyrqK/Optxxp2pmVh+fmJ97slxSRyzUg= +google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:+Rvu7ElI+aLzyDQhpHMFMMltsD6m7nqpuWDd2CwJw3k= +google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe h1:0poefMBYvYbs7g5UkjS6HcxBPaTRAmznle9jnxYoAI8= +google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac h1:nUQEQmH/csSvFECKYRv6HWEyypysidKl2I6Qpsglq/0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA= +google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0= +google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/go-jose/go-jose.v2 v2.6.1 h1:qEzJlIDmG9q5VO0M/o8tGS65QMHMS1w01TQJB1VPJ4U= +gopkg.in/go-jose/go-jose.v2 v2.6.1/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI= gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY= gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 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= +gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= +gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= +k8s.io/klog/v2 v2.120.0 h1:z+q5mfovBj1fKFxiRzsa2DsJLPIVMk/KFL81LMOfK+8= +k8s.io/klog/v2 v2.120.0/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +software.sslmate.com/src/go-pkcs12 v0.2.0 h1:nlFkj7bTysH6VkC4fGphtjXRbezREPgrHuJG20hBGPE= +software.sslmate.com/src/go-pkcs12 v0.2.0/go.mod h1:23rNcYsMabIc1otwLpTkCCPwUq6kQsTyowttG/as0kQ= diff --git a/pkg/cmd/attestation/api/attestation.go b/pkg/cmd/attestation/api/attestation.go new file mode 100644 index 000000000..2b96a51fc --- /dev/null +++ b/pkg/cmd/attestation/api/attestation.go @@ -0,0 +1,33 @@ +package api + +import ( + "fmt" + + "github.com/sigstore/sigstore-go/pkg/bundle" +) + +const ( + GetAttestationByRepoAndSubjectDigestPath = "repos/%s/attestations/%s" + GetAttestationByOwnerAndSubjectDigestPath = "orgs/%s/attestations/%s" +) + +type ErrNoAttestations struct { + name string + digest string +} + +func (e ErrNoAttestations) Error() string { + return fmt.Sprintf("no attestations found for digest %s in %s", e.name, e.digest) +} + +func newErrNoAttestations(name, digest string) ErrNoAttestations { + return ErrNoAttestations{name, digest} +} + +type Attestation struct { + Bundle *bundle.ProtobufBundle `json:"bundle"` +} + +type AttestationsResponse struct { + Attestations []*Attestation `json:"attestations"` +} diff --git a/pkg/cmd/attestation/api/client.go b/pkg/cmd/attestation/api/client.go new file mode 100644 index 000000000..b0cafa6e9 --- /dev/null +++ b/pkg/cmd/attestation/api/client.go @@ -0,0 +1,104 @@ +package api + +import ( + "fmt" + "io" + "net/http" + "strings" + + "github.com/cli/cli/v2/api" + ioconfig "github.com/cli/cli/v2/pkg/cmd/attestation/io" + "github.com/cli/go-gh/v2/pkg/auth" +) + +const ( + DefaultLimit = 30 + maxLimitForFlag = 1000 + maxLimitForFetch = 100 +) + +type apiClient interface { + RESTWithNext(hostname, method, p string, body io.Reader, data interface{}) (string, error) +} + +type Client interface { + GetByRepoAndDigest(repo, digest string, limit int) ([]*Attestation, error) + GetByOwnerAndDigest(owner, digest string, limit int) ([]*Attestation, error) +} + +type LiveClient struct { + api apiClient + host string + logger *ioconfig.Handler +} + +func NewLiveClient(hc *http.Client, l *ioconfig.Handler) *LiveClient { + host, _ := auth.DefaultHost() + + return &LiveClient{ + api: api.NewClientFromHTTP(hc), + host: strings.TrimSuffix(host, "/"), + logger: l, + } +} + +func (c *LiveClient) BuildRepoAndDigestURL(repo, digest string) string { + repo = strings.Trim(repo, "/") + return fmt.Sprintf(GetAttestationByRepoAndSubjectDigestPath, repo, digest) +} + +// GetByRepoAndDigest fetches the attestation by repo and digest +func (c *LiveClient) GetByRepoAndDigest(repo, digest string, limit int) ([]*Attestation, error) { + url := c.BuildRepoAndDigestURL(repo, digest) + return c.getAttestations(url, repo, digest, limit) +} + +func (c *LiveClient) BuildOwnerAndDigestURL(owner, digest string) string { + owner = strings.Trim(owner, "/") + return fmt.Sprintf(GetAttestationByOwnerAndSubjectDigestPath, owner, digest) +} + +// GetByOwnerAndDigest fetches attestation by owner and digest +func (c *LiveClient) GetByOwnerAndDigest(owner, digest string, limit int) ([]*Attestation, error) { + url := c.BuildOwnerAndDigestURL(owner, digest) + return c.getAttestations(url, owner, digest, limit) +} + +func (c *LiveClient) getAttestations(url, name, digest string, limit int) ([]*Attestation, error) { + c.logger.VerbosePrintf("Fetching attestations for artifact digest %s\n\n", digest) + + perPage := limit + if perPage <= 0 || perPage > maxLimitForFlag { + return nil, fmt.Errorf("limit must be greater than 0 and less than or equal to %d", maxLimitForFlag) + } + + if perPage > maxLimitForFetch { + perPage = maxLimitForFetch + } + + // ref: https://github.com/cli/go-gh/blob/d32c104a9a25c9de3d7c7b07a43ae0091441c858/example_gh_test.go#L96 + url = fmt.Sprintf("%s?per_page=%d", url, perPage) + + var attestations []*Attestation + var resp AttestationsResponse + var err error + // if no attestation or less than limit, then keep fetching + for url != "" && len(attestations) < limit { + url, err = c.api.RESTWithNext(c.host, http.MethodGet, url, nil, &resp) + if err != nil { + return nil, err + } + + attestations = append(attestations, resp.Attestations...) + } + + if len(attestations) == 0 { + return nil, newErrNoAttestations(name, digest) + } + + if len(attestations) > limit { + return attestations[:limit], nil + } + + return attestations, nil +} diff --git a/pkg/cmd/attestation/api/client_test.go b/pkg/cmd/attestation/api/client_test.go new file mode 100644 index 000000000..7044a1cc5 --- /dev/null +++ b/pkg/cmd/attestation/api/client_test.go @@ -0,0 +1,174 @@ +package api + +import ( + "testing" + + "github.com/cli/cli/v2/pkg/cmd/attestation/io" + + "github.com/stretchr/testify/require" +) + +const ( + testRepo = "github/example" + testOwner = "github" + testDigest = "sha256:12313213" +) + +func NewClientWithMockGHClient(hasNextPage bool) Client { + fetcher := mockDataGenerator{ + NumAttestations: 5, + } + l := io.NewTestHandler() + + if hasNextPage { + return &LiveClient{ + api: mockAPIClient{ + OnRESTWithNext: fetcher.OnRESTSuccessWithNextPage, + }, + logger: l, + } + } + + return &LiveClient{ + api: mockAPIClient{ + OnRESTWithNext: fetcher.OnRESTSuccess, + }, + logger: l, + } +} + +func TestGetURL(t *testing.T) { + c := LiveClient{} + + testData := []struct { + repo string + digest string + expected string + }{ + {repo: "/github/example/", digest: "sha256:12313213", expected: "repos/github/example/attestations/sha256:12313213"}, + {repo: "/github/example", digest: "sha256:12313213", expected: "repos/github/example/attestations/sha256:12313213"}, + } + + for _, data := range testData { + s := c.BuildRepoAndDigestURL(data.repo, data.digest) + require.Equal(t, data.expected, s) + } +} + +func TestGetByDigest(t *testing.T) { + c := NewClientWithMockGHClient(false) + attestations, err := c.GetByRepoAndDigest(testRepo, testDigest, DefaultLimit) + require.NoError(t, err) + + require.Equal(t, 5, len(attestations)) + bundle := (attestations)[0].Bundle + require.Equal(t, bundle.GetMediaType(), "application/vnd.dev.sigstore.bundle+json;version=0.1") + + attestations, err = c.GetByOwnerAndDigest(testOwner, testDigest, DefaultLimit) + require.NoError(t, err) + + require.Equal(t, 5, len(attestations)) + bundle = (attestations)[0].Bundle + require.Equal(t, bundle.GetMediaType(), "application/vnd.dev.sigstore.bundle+json;version=0.1") +} + +func TestGetByDigestGreaterThanLimit(t *testing.T) { + c := NewClientWithMockGHClient(false) + + limit := 3 + // The method should return five results when the limit is not set + attestations, err := c.GetByRepoAndDigest(testRepo, testDigest, limit) + require.NoError(t, err) + + require.Equal(t, 3, len(attestations)) + bundle := (attestations)[0].Bundle + require.Equal(t, bundle.GetMediaType(), "application/vnd.dev.sigstore.bundle+json;version=0.1") + + attestations, err = c.GetByOwnerAndDigest(testOwner, testDigest, limit) + require.NoError(t, err) + + require.Equal(t, len(attestations), limit) + bundle = (attestations)[0].Bundle + require.Equal(t, bundle.GetMediaType(), "application/vnd.dev.sigstore.bundle+json;version=0.1") +} + +func TestGetByDigestWithNextPage(t *testing.T) { + c := NewClientWithMockGHClient(true) + attestations, err := c.GetByRepoAndDigest(testRepo, testDigest, DefaultLimit) + require.NoError(t, err) + + require.Equal(t, len(attestations), 10) + bundle := (attestations)[0].Bundle + require.Equal(t, bundle.GetMediaType(), "application/vnd.dev.sigstore.bundle+json;version=0.1") + + attestations, err = c.GetByOwnerAndDigest(testOwner, testDigest, DefaultLimit) + require.NoError(t, err) + + require.Equal(t, len(attestations), 10) + bundle = (attestations)[0].Bundle + require.Equal(t, bundle.GetMediaType(), "application/vnd.dev.sigstore.bundle+json;version=0.1") +} + +func TestGetByDigestGreaterThanLimitWithNextPage(t *testing.T) { + c := NewClientWithMockGHClient(true) + + limit := 7 + // The method should return five results when the limit is not set + attestations, err := c.GetByRepoAndDigest(testRepo, testDigest, limit) + require.NoError(t, err) + + require.Equal(t, len(attestations), limit) + bundle := (attestations)[0].Bundle + require.Equal(t, bundle.GetMediaType(), "application/vnd.dev.sigstore.bundle+json;version=0.1") + + attestations, err = c.GetByOwnerAndDigest(testOwner, testDigest, limit) + require.NoError(t, err) + + require.Equal(t, len(attestations), limit) + bundle = (attestations)[0].Bundle + require.Equal(t, bundle.GetMediaType(), "application/vnd.dev.sigstore.bundle+json;version=0.1") +} + +func TestGetByDigest_NoAttestationsFound(t *testing.T) { + fetcher := mockDataGenerator{ + NumAttestations: 5, + } + + c := LiveClient{ + api: mockAPIClient{ + OnRESTWithNext: fetcher.OnRESTWithNextNoAttestations, + }, + logger: io.NewTestHandler(), + } + + attestations, err := c.GetByRepoAndDigest(testRepo, testDigest, DefaultLimit) + require.Error(t, err) + require.IsType(t, ErrNoAttestations{}, err) + require.Nil(t, attestations) + + attestations, err = c.GetByOwnerAndDigest(testOwner, testDigest, DefaultLimit) + require.Error(t, err) + require.IsType(t, ErrNoAttestations{}, err) + require.Nil(t, attestations) +} + +func TestGetByDigest_Error(t *testing.T) { + fetcher := mockDataGenerator{ + NumAttestations: 5, + } + + c := LiveClient{ + api: mockAPIClient{ + OnRESTWithNext: fetcher.OnRESTWithNextError, + }, + logger: io.NewTestHandler(), + } + + attestations, err := c.GetByRepoAndDigest(testRepo, testDigest, DefaultLimit) + require.Error(t, err) + require.Nil(t, attestations) + + attestations, err = c.GetByOwnerAndDigest(testOwner, testDigest, DefaultLimit) + require.Error(t, err) + require.Nil(t, attestations) +} diff --git a/pkg/cmd/attestation/api/mock_apiClient_test.go b/pkg/cmd/attestation/api/mock_apiClient_test.go new file mode 100644 index 000000000..1d4f61cd9 --- /dev/null +++ b/pkg/cmd/attestation/api/mock_apiClient_test.go @@ -0,0 +1,89 @@ +package api + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "strings" +) + +type mockAPIClient struct { + OnRESTWithNext func(hostname, method, p string, body io.Reader, data interface{}) (string, error) +} + +func (m mockAPIClient) RESTWithNext(hostname, method, p string, body io.Reader, data interface{}) (string, error) { + return m.OnRESTWithNext(hostname, method, p, body, data) +} + +type mockDataGenerator struct { + NumAttestations int +} + +func (m mockDataGenerator) OnRESTSuccess(hostname, method, p string, body io.Reader, data interface{}) (string, error) { + return m.OnRESTWithNextSuccessHelper(hostname, method, p, body, data, false) +} + +func (m mockDataGenerator) OnRESTSuccessWithNextPage(hostname, method, p string, body io.Reader, data interface{}) (string, error) { + // if path doesn't contain after, it means first time hitting the mock server + // so return the first page and return the link header in the response + if !strings.Contains(p, "after") { + return m.OnRESTWithNextSuccessHelper(hostname, method, p, body, data, true) + } + + // if path contain after, it means second time hitting the mock server and will not return the link header + return m.OnRESTWithNextSuccessHelper(hostname, method, p, body, data, false) +} + +func (m mockDataGenerator) OnRESTWithNextSuccessHelper(hostname, method, p string, body io.Reader, data interface{}, hasNext bool) (string, error) { + atts := make([]*Attestation, m.NumAttestations) + for j := 0; j < m.NumAttestations; j++ { + att := makeTestAttestation() + atts[j] = &att + } + + resp := AttestationsResponse{ + Attestations: atts, + } + + // // Convert the attestations to JSON + b, err := json.Marshal(resp) + if err != nil { + return "", err + } + + err = json.Unmarshal(b, &data) + if err != nil { + return "", err + } + + if hasNext { + // return a link header with the next page + return fmt.Sprintf("<%s&after=2>; rel=\"next\"", p), nil + } + + return "", nil +} + +func (m mockDataGenerator) OnRESTWithNextNoAttestations(hostname, method, p string, body io.Reader, data interface{}) (string, error) { + resp := AttestationsResponse{ + Attestations: make([]*Attestation, 0), + } + + // // Convert the attestations to JSON + b, err := json.Marshal(resp) + if err != nil { + return "", err + } + + err = json.Unmarshal(b, &data) + if err != nil { + return "", err + } + + return "", nil +} + +func (m mockDataGenerator) OnRESTWithNextError(hostname, method, p string, body io.Reader, data interface{}) (string, error) { + return "", errors.New("failed to get attestations") +} diff --git a/pkg/cmd/attestation/api/mock_client.go b/pkg/cmd/attestation/api/mock_client.go new file mode 100644 index 000000000..96a64e4fc --- /dev/null +++ b/pkg/cmd/attestation/api/mock_client.go @@ -0,0 +1,71 @@ +package api + +import ( + "encoding/json" + "fmt" + "os" + + "github.com/sigstore/sigstore-go/pkg/bundle" +) + +type MockClient struct { + OnGetByRepoAndDigest func(repo, digest string, limit int) ([]*Attestation, error) + OnGetByOwnerAndDigest func(owner, digest string, limit int) ([]*Attestation, error) +} + +func (m MockClient) GetByRepoAndDigest(repo, digest string, limit int) ([]*Attestation, error) { + return m.OnGetByRepoAndDigest(repo, digest, limit) +} + +func (m MockClient) GetByOwnerAndDigest(owner, digest string, limit int) ([]*Attestation, error) { + return m.OnGetByOwnerAndDigest(owner, digest, limit) +} + +func makeTestAttestation() Attestation { + bundleBytes, err := os.ReadFile("../test/data/sigstore-js-2.1.0-bundle.json") + if err != nil { + panic(err) + } + + var b *bundle.ProtobufBundle + err = json.Unmarshal(bundleBytes, &b) + if err != nil { + panic(err) + } + + return Attestation{Bundle: b} +} + +func OnGetByRepoAndDigestSuccess(repo, digest string, limit int) ([]*Attestation, error) { + att1 := makeTestAttestation() + att2 := makeTestAttestation() + return []*Attestation{&att1, &att2}, nil +} + +func OnGetByRepoAndDigestFailure(repo, digest string, limit int) ([]*Attestation, error) { + return nil, fmt.Errorf("failed to fetch by repo and digest") +} + +func OnGetByOwnerAndDigestSuccess(owner, digest string, limit int) ([]*Attestation, error) { + att1 := makeTestAttestation() + att2 := makeTestAttestation() + return []*Attestation{&att1, &att2}, nil +} + +func OnGetByOwnerAndDigestFailure(owner, digest string, limit int) ([]*Attestation, error) { + return nil, fmt.Errorf("failed to fetch by owner and digest") +} + +func NewTestClient() *MockClient { + return &MockClient{ + OnGetByRepoAndDigest: OnGetByRepoAndDigestSuccess, + OnGetByOwnerAndDigest: OnGetByOwnerAndDigestSuccess, + } +} + +func NewFailTestClient() *MockClient { + return &MockClient{ + OnGetByRepoAndDigest: OnGetByRepoAndDigestFailure, + OnGetByOwnerAndDigest: OnGetByOwnerAndDigestFailure, + } +} diff --git a/pkg/cmd/attestation/artifact/artifact.go b/pkg/cmd/attestation/artifact/artifact.go new file mode 100644 index 000000000..de354b947 --- /dev/null +++ b/pkg/cmd/attestation/artifact/artifact.go @@ -0,0 +1,79 @@ +package artifact + +import ( + "fmt" + "net/url" + "os" + "path/filepath" + "strings" + + "github.com/cli/cli/v2/pkg/cmd/attestation/artifact/oci" +) + +type artifactType int + +const ( + ociArtifactType artifactType = iota + fileArtifactType +) + +// DigestedArtifact abstracts the software artifact being verified +type DigestedArtifact struct { + URL string + digest string + digestAlg string +} + +func normalizeReference(reference string, pathSeparator rune) (normalized string, artifactType artifactType, err error) { + switch { + case strings.HasPrefix(reference, "oci://"): + return reference[6:], ociArtifactType, nil + case strings.HasPrefix(reference, "file://"): + uri, err := url.ParseRequestURI(reference) + if err != nil { + return "", 0, fmt.Errorf("failed to parse reference URI: %v", err) + } + var path string + if pathSeparator == '/' { + // Unix paths use forward slashes like URIs, so no need to modify + path = uri.Path + } else { + // Windows paths should be normalized to use backslashes + path = strings.ReplaceAll(uri.Path, "/", string(pathSeparator)) + // Remove leading slash from Windows paths if present + if strings.HasPrefix(path, string(pathSeparator)) { + path = path[1:] + } + } + return filepath.Clean(path), fileArtifactType, nil + } + // Treat any other reference as a local file path + return filepath.Clean(reference), fileArtifactType, nil +} + +func NewDigestedArtifact(client oci.Client, reference, digestAlg string) (artifact *DigestedArtifact, err error) { + normalized, artifactType, err := normalizeReference(reference, os.PathSeparator) + if err != nil { + return nil, err + } + if artifactType == ociArtifactType { + // TODO: should we allow custom digestAlg for OCI artifacts? + return digestContainerImageArtifact(normalized, client) + } + return digestLocalFileArtifact(normalized, digestAlg) +} + +// Digest returns the artifact's digest +func (a *DigestedArtifact) Digest() string { + return a.digest +} + +// Algorithm returns the artifact's algorithm +func (a *DigestedArtifact) Algorithm() string { + return a.digestAlg +} + +// DigestWithAlg returns the digest:algorithm of the artifact +func (a *DigestedArtifact) DigestWithAlg() string { + return fmt.Sprintf("%s:%s", a.digestAlg, a.digest) +} diff --git a/pkg/cmd/attestation/artifact/artifact_posix_test.go b/pkg/cmd/attestation/artifact/artifact_posix_test.go new file mode 100644 index 000000000..31e9cb7e7 --- /dev/null +++ b/pkg/cmd/attestation/artifact/artifact_posix_test.go @@ -0,0 +1,99 @@ +//go:build !windows +// +build !windows + +package artifact + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestNormalizeReference(t *testing.T) { + testCases := []struct { + name string + reference string + pathSeparator rune + expectedResult string + expectedType artifactType + expectedError bool + }{ + { + name: "file reference without scheme", + reference: "/path/to/file", + pathSeparator: '/', + expectedResult: "/path/to/file", + expectedType: fileArtifactType, + expectedError: false, + }, + { + name: "file scheme uri with %20", + reference: "file:///path/to/file%20with%20spaces", + pathSeparator: '/', + expectedResult: "/path/to/file with spaces", + expectedType: fileArtifactType, + expectedError: false, + }, + { + name: "windows file reference without scheme", + reference: `c:\path\to\file`, + pathSeparator: '\\', + expectedResult: `c:\path\to\file`, + expectedType: fileArtifactType, + expectedError: false, + }, + { + name: "file reference with scheme", + reference: "file:///path/to/file", + pathSeparator: '/', + expectedResult: "/path/to/file", + expectedType: fileArtifactType, + expectedError: false, + }, + { + name: "windows path", + reference: "file:///C:/path/to/file", + pathSeparator: '\\', + expectedResult: `C:\path\to\file`, + expectedType: fileArtifactType, + expectedError: false, + }, + { + name: "windows path with backslashes", + reference: "file:///C:\\path\\to\\file", + pathSeparator: '\\', + expectedResult: `C:\path\to\file`, + expectedType: fileArtifactType, + expectedError: false, + }, + { + name: "oci reference", + reference: "oci://example.com/repo:tag", + pathSeparator: '/', + expectedResult: "example.com/repo:tag", + expectedType: ociArtifactType, + expectedError: false, + }, + { + name: "oci reference with digest", + reference: "oci://example.com/repo@sha256:abcdef1234567890", + pathSeparator: '/', + expectedResult: "example.com/repo@sha256:abcdef1234567890", + expectedType: ociArtifactType, + expectedError: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result, artifactType, err := normalizeReference(tc.reference, tc.pathSeparator) + if tc.expectedError { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Equal(t, tc.expectedResult, result) + require.Equal(t, tc.expectedType, artifactType) + } + }) + } +} diff --git a/pkg/cmd/attestation/artifact/artifact_windows_test.go b/pkg/cmd/attestation/artifact/artifact_windows_test.go new file mode 100644 index 000000000..46995f226 --- /dev/null +++ b/pkg/cmd/attestation/artifact/artifact_windows_test.go @@ -0,0 +1,59 @@ +//go:build windows +// +build windows + +package artifact + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestNormalizeReference(t *testing.T) { + testCases := []struct { + name string + reference string + pathSeparator rune + expectedResult string + expectedType artifactType + expectedError bool + }{ + { + name: "windows file reference without scheme", + reference: `c:\path\to\file`, + pathSeparator: '\\', + expectedResult: `c:\path\to\file`, + expectedType: fileArtifactType, + expectedError: false, + }, + { + name: "windows path", + reference: "file:///C:/path/to/file", + pathSeparator: '\\', + expectedResult: `C:\path\to\file`, + expectedType: fileArtifactType, + expectedError: false, + }, + { + name: "windows path with backslashes", + reference: "file:///C:\\path\\to\\file", + pathSeparator: '\\', + expectedResult: `C:\path\to\file`, + expectedType: fileArtifactType, + expectedError: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result, artifactType, err := normalizeReference(tc.reference, tc.pathSeparator) + if tc.expectedError { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Equal(t, tc.expectedResult, result) + require.Equal(t, tc.expectedType, artifactType) + } + }) + } +} diff --git a/pkg/cmd/attestation/artifact/digest/digest.go b/pkg/cmd/attestation/artifact/digest/digest.go new file mode 100644 index 000000000..e48fb1d0d --- /dev/null +++ b/pkg/cmd/attestation/artifact/digest/digest.go @@ -0,0 +1,53 @@ +package digest + +import ( + "crypto/sha256" + "crypto/sha512" + "encoding/hex" + "fmt" + "hash" + "io" +) + +const ( + SHA256DigestAlgorithm = "sha256" + SHA512DigestAlgorithm = "sha512" +) + +var ( + errUnsupportedAlgorithm = fmt.Errorf("unsupported digest algorithm") + validDigestAlgorithms = [...]string{SHA256DigestAlgorithm, SHA512DigestAlgorithm} +) + +// IsValidDigestAlgorithm returns true if the provided algorithm is supported +func IsValidDigestAlgorithm(alg string) bool { + for _, a := range validDigestAlgorithms { + if a == alg { + return true + } + } + return false +} + +// ValidDigestAlgorithms returns a list of supported digest algorithms +func ValidDigestAlgorithms() []string { + return validDigestAlgorithms[:] +} + +func CalculateDigestWithAlgorithm(r io.Reader, alg string) (string, error) { + var h hash.Hash + switch alg { + case SHA256DigestAlgorithm: + h = sha256.New() + case SHA512DigestAlgorithm: + h = sha512.New() + default: + return "", errUnsupportedAlgorithm + } + + if _, err := io.Copy(h, r); err != nil { + return "", fmt.Errorf("failed to calculate digest: %v", err) + } + digest := h.Sum(nil) + return hex.EncodeToString(digest), nil +} diff --git a/pkg/cmd/attestation/artifact/digest/digest_test.go b/pkg/cmd/attestation/artifact/digest/digest_test.go new file mode 100644 index 000000000..bcfd2c1ac --- /dev/null +++ b/pkg/cmd/attestation/artifact/digest/digest_test.go @@ -0,0 +1,46 @@ +package digest + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestArtifactDigestWithAlgorithm(t *testing.T) { + testString := "deadbeef" + sha512TestDigest := "113a3bc783d851fc0373214b19ea7be9fa3de541ecb9fe026d52c603e8ea19c174cc0e9705f8b90d312212c0c3a6d8453ddfb3e3141409cf4bedc8ef033590b4" + sha256TestDigest := "2baf1f40105d9501fe319a8ec463fdf4325a2a5df445adf3f572f626253678c9" + + t.Run("sha256", func(t *testing.T) { + reader := strings.NewReader(testString) + digest, err := CalculateDigestWithAlgorithm(reader, "sha256") + assert.Nil(t, err) + assert.Equal(t, sha256TestDigest, digest) + }) + + t.Run("sha512", func(t *testing.T) { + reader := strings.NewReader(testString) + digest, err := CalculateDigestWithAlgorithm(reader, "sha512") + assert.Nil(t, err) + assert.Equal(t, sha512TestDigest, digest) + }) + + t.Run("fail with sha384", func(t *testing.T) { + reader := strings.NewReader(testString) + _, err := CalculateDigestWithAlgorithm(reader, "sha384") + require.Error(t, err) + require.ErrorAs(t, err, &errUnsupportedAlgorithm) + }) +} + +func TestValidDigestAlgorithms(t *testing.T) { + t.Run("includes sha256", func(t *testing.T) { + assert.Contains(t, ValidDigestAlgorithms(), "sha256") + }) + + t.Run("includes sha512", func(t *testing.T) { + assert.Contains(t, ValidDigestAlgorithms(), "sha512") + }) +} diff --git a/pkg/cmd/attestation/artifact/file.go b/pkg/cmd/attestation/artifact/file.go new file mode 100644 index 000000000..789a92a5d --- /dev/null +++ b/pkg/cmd/attestation/artifact/file.go @@ -0,0 +1,25 @@ +package artifact + +import ( + "fmt" + "os" + + "github.com/cli/cli/v2/pkg/cmd/attestation/artifact/digest" +) + +func digestLocalFileArtifact(filename, digestAlg string) (*DigestedArtifact, error) { + data, err := os.Open(filename) + if err != nil { + return nil, fmt.Errorf("failed to get open local artifact: %v", err) + } + defer data.Close() + digest, err := digest.CalculateDigestWithAlgorithm(data, digestAlg) + if err != nil { + return nil, fmt.Errorf("failed to calculate local artifact digest: %v", err) + } + return &DigestedArtifact{ + URL: fmt.Sprintf("file://%s", filename), + digest: digest, + digestAlg: digestAlg, + }, nil +} diff --git a/pkg/cmd/attestation/artifact/image.go b/pkg/cmd/attestation/artifact/image.go new file mode 100644 index 000000000..2af13e723 --- /dev/null +++ b/pkg/cmd/attestation/artifact/image.go @@ -0,0 +1,28 @@ +package artifact + +import ( + "fmt" + + "github.com/cli/cli/v2/pkg/cmd/attestation/artifact/oci" + "github.com/distribution/reference" +) + +func digestContainerImageArtifact(url string, client oci.Client) (*DigestedArtifact, error) { + // try to parse the url as a valid registry reference + named, err := reference.Parse(url) + if err != nil { + // cannot be parsed as a registry reference + return nil, fmt.Errorf("artifact %s is not a valid registry reference: %v", url, err) + } + + digest, err := client.GetImageDigest(named.String()) + if err != nil { + return nil, err + } + + return &DigestedArtifact{ + URL: fmt.Sprintf("oci://%s", named.String()), + digest: digest.Hex, + digestAlg: digest.Algorithm, + }, nil +} diff --git a/pkg/cmd/attestation/artifact/image_test.go b/pkg/cmd/attestation/artifact/image_test.go new file mode 100644 index 000000000..5ea5f9a37 --- /dev/null +++ b/pkg/cmd/attestation/artifact/image_test.go @@ -0,0 +1,52 @@ +package artifact + +import ( + "fmt" + "testing" + + "github.com/cli/cli/v2/pkg/cmd/attestation/artifact/oci" + "github.com/stretchr/testify/require" +) + +func TestDigestContainerImageArtifact(t *testing.T) { + expectedDigest := "1234567890abcdef" + client := oci.MockClient{} + url := "example.com/repo:tag" + digestedArtifact, err := digestContainerImageArtifact(url, client) + require.NoError(t, err) + require.Equal(t, fmt.Sprintf("oci://%s", url), digestedArtifact.URL) + require.Equal(t, expectedDigest, digestedArtifact.digest) + require.Equal(t, "sha256", digestedArtifact.digestAlg) +} + +func TestParseImageRefFailure(t *testing.T) { + client := oci.ReferenceFailClient{} + url := "example.com/repo:tag" + _, err := digestContainerImageArtifact(url, client) + require.Error(t, err) +} + +func TestFetchImageFailure(t *testing.T) { + testcase := []struct { + name string + client oci.Client + expectedErr error + }{ + { + name: "Fail to authorize with registry", + client: oci.AuthFailClient{}, + expectedErr: oci.ErrRegistryAuthz, + }, + { + name: "Fail to fetch image due to denial", + client: oci.DeniedClient{}, + expectedErr: oci.ErrDenied, + }, + } + + for _, tc := range testcase { + url := "example.com/repo:tag" + _, err := digestContainerImageArtifact(url, tc.client) + require.ErrorIs(t, err, tc.expectedErr) + } +} diff --git a/pkg/cmd/attestation/artifact/oci/client.go b/pkg/cmd/attestation/artifact/oci/client.go new file mode 100644 index 000000000..5b8d8cf7a --- /dev/null +++ b/pkg/cmd/attestation/artifact/oci/client.go @@ -0,0 +1,70 @@ +package oci + +import ( + "errors" + "fmt" + + "github.com/google/go-containerregistry/pkg/authn" + "github.com/google/go-containerregistry/pkg/name" + "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/remote" + "github.com/google/go-containerregistry/pkg/v1/remote/transport" +) + +var ErrDenied = errors.New("the provided token was denied access to the requested resource, please check the token's expiration and repository access") +var ErrRegistryAuthz = errors.New("remote registry authorization failed, please authenticate with the registry and try again") + +type Client interface { + GetImageDigest(imgName string) (*v1.Hash, error) +} + +func checkForUnauthorizedOrDeniedErr(err transport.Error) error { + for _, diagnostic := range err.Errors { + switch diagnostic.Code { + case transport.UnauthorizedErrorCode: + return ErrRegistryAuthz + case transport.DeniedErrorCode: + return ErrDenied + } + } + return nil +} + +type LiveClient struct { + parseReference func(string, ...name.Option) (name.Reference, error) + get func(name.Reference, ...remote.Option) (*remote.Descriptor, error) +} + +// where name is formed like ghcr.io/github/my-image-repo +func (c LiveClient) GetImageDigest(imgName string) (*v1.Hash, error) { + name, err := c.parseReference(imgName) + if err != nil { + return nil, fmt.Errorf("failed to create image tag: %v", err) + } + + // The user must already be authenticated with the container registry + // The authn.DefaultKeychain argument indicates that Get should checks the + // user's configuration for the registry credentials + desc, err := c.get(name, remote.WithAuthFromKeychain(authn.DefaultKeychain)) + if err != nil { + var transportErr *transport.Error + if errors.As(err, &transportErr) { + if accessErr := checkForUnauthorizedOrDeniedErr(*transportErr); accessErr != nil { + return nil, accessErr + } + } + return nil, fmt.Errorf("failed to fetch remote image: %v", err) + } + + return &desc.Digest, nil +} + +// Unlike other parts of this command set, we cannot pass a custom HTTP client +// to the go-containerregistry library. This means we have limited visibility +// into the HTTP requests being made to container registries. +func NewLiveClient() *LiveClient { + return &LiveClient{ + parseReference: name.ParseReference, + get: remote.Get, + } +} diff --git a/pkg/cmd/attestation/artifact/oci/client_test.go b/pkg/cmd/attestation/artifact/oci/client_test.go new file mode 100644 index 000000000..9aa415c47 --- /dev/null +++ b/pkg/cmd/attestation/artifact/oci/client_test.go @@ -0,0 +1,83 @@ +package oci + +import ( + "fmt" + "testing" + + "github.com/google/go-containerregistry/pkg/name" + "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/remote" + "github.com/google/go-containerregistry/pkg/v1/remote/transport" + + "github.com/stretchr/testify/require" +) + +func TestGetImageDigest_Success(t *testing.T) { + expectedDigest := v1.Hash{ + Hex: "1234567890abcdef", + Algorithm: "sha256", + } + + c := LiveClient{ + parseReference: func(string, ...name.Option) (name.Reference, error) { + return name.Tag{}, nil + }, + get: func(name.Reference, ...remote.Option) (*remote.Descriptor, error) { + d := remote.Descriptor{} + d.Digest = expectedDigest + + return &d, nil + }, + } + + digest, err := c.GetImageDigest("test") + require.NoError(t, err) + require.Equal(t, &expectedDigest, digest) +} + +func TestGetImageDigest_ReferenceFail(t *testing.T) { + c := LiveClient{ + parseReference: func(string, ...name.Option) (name.Reference, error) { + return nil, fmt.Errorf("failed to parse reference") + }, + get: func(name.Reference, ...remote.Option) (*remote.Descriptor, error) { + return nil, nil + }, + } + + digest, err := c.GetImageDigest("test") + require.Error(t, err) + require.Nil(t, digest) +} + +func TestGetImageDigest_AuthFail(t *testing.T) { + c := LiveClient{ + parseReference: func(string, ...name.Option) (name.Reference, error) { + return name.Tag{}, nil + }, + get: func(name.Reference, ...remote.Option) (*remote.Descriptor, error) { + return nil, &transport.Error{Errors: []transport.Diagnostic{{Code: transport.UnauthorizedErrorCode}}} + }, + } + + digest, err := c.GetImageDigest("test") + require.Error(t, err) + require.ErrorIs(t, err, ErrRegistryAuthz) + require.Nil(t, digest) +} + +func TestGetImageDigest_Denied(t *testing.T) { + c := LiveClient{ + parseReference: func(string, ...name.Option) (name.Reference, error) { + return name.Tag{}, nil + }, + get: func(name.Reference, ...remote.Option) (*remote.Descriptor, error) { + return nil, &transport.Error{Errors: []transport.Diagnostic{{Code: transport.DeniedErrorCode}}} + }, + } + + digest, err := c.GetImageDigest("test") + require.Error(t, err) + require.ErrorIs(t, err, ErrDenied) + require.Nil(t, digest) +} diff --git a/pkg/cmd/attestation/artifact/oci/mock_client.go b/pkg/cmd/attestation/artifact/oci/mock_client.go new file mode 100644 index 000000000..24368dec8 --- /dev/null +++ b/pkg/cmd/attestation/artifact/oci/mock_client.go @@ -0,0 +1,34 @@ +package oci + +import ( + "fmt" + + "github.com/google/go-containerregistry/pkg/v1" +) + +type MockClient struct{} + +func (c MockClient) GetImageDigest(imgName string) (*v1.Hash, error) { + return &v1.Hash{ + Hex: "1234567890abcdef", + Algorithm: "sha256", + }, nil +} + +type ReferenceFailClient struct{} + +func (c ReferenceFailClient) GetImageDigest(imgName string) (*v1.Hash, error) { + return nil, fmt.Errorf("failed to parse reference") +} + +type AuthFailClient struct{} + +func (c AuthFailClient) GetImageDigest(imgName string) (*v1.Hash, error) { + return nil, ErrRegistryAuthz +} + +type DeniedClient struct{} + +func (c DeniedClient) GetImageDigest(imgName string) (*v1.Hash, error) { + return nil, ErrDenied +} diff --git a/pkg/cmd/attestation/attestation.go b/pkg/cmd/attestation/attestation.go new file mode 100644 index 000000000..9b3a0cd2c --- /dev/null +++ b/pkg/cmd/attestation/attestation.go @@ -0,0 +1,28 @@ +package attestation + +import ( + "github.com/cli/cli/v2/pkg/cmd/attestation/download" + "github.com/cli/cli/v2/pkg/cmd/attestation/inspect" + "github.com/cli/cli/v2/pkg/cmd/attestation/tufrootverify" + "github.com/cli/cli/v2/pkg/cmd/attestation/verify" + "github.com/cli/cli/v2/pkg/cmdutil" + + "github.com/spf13/cobra" +) + +func NewCmdAttestation(f *cmdutil.Factory) *cobra.Command { + root := &cobra.Command{ + Use: "attestation [subcommand]", + Short: "Work with artifact attestations", + Aliases: []string{"at"}, + Hidden: true, + Long: "Download and verify artifact attestations.", + } + + root.AddCommand(download.NewDownloadCmd(f, nil)) + root.AddCommand(inspect.NewInspectCmd(f, nil)) + root.AddCommand(verify.NewVerifyCmd(f, nil)) + root.AddCommand(tufrootverify.NewTUFRootVerifyCmd(f, nil)) + + return root +} diff --git a/pkg/cmd/attestation/auth/host.go b/pkg/cmd/attestation/auth/host.go new file mode 100644 index 000000000..998dcb7f5 --- /dev/null +++ b/pkg/cmd/attestation/auth/host.go @@ -0,0 +1,17 @@ +package auth + +import ( + "errors" + + "github.com/cli/go-gh/v2/pkg/auth" +) + +var ErrUnsupportedHost = errors.New("The GH_HOST environment variable is set to a custom GitHub host. gh attestation does not currently support custom GitHub Enterprise hosts") + +func IsHostSupported() error { + host, _ := auth.DefaultHost() + if host != "github.com" { + return ErrUnsupportedHost + } + return nil +} diff --git a/pkg/cmd/attestation/download/download.go b/pkg/cmd/attestation/download/download.go new file mode 100644 index 000000000..2f40970fc --- /dev/null +++ b/pkg/cmd/attestation/download/download.go @@ -0,0 +1,148 @@ +package download + +import ( + "errors" + "fmt" + + "github.com/cli/cli/v2/pkg/cmd/attestation/api" + "github.com/cli/cli/v2/pkg/cmd/attestation/artifact" + "github.com/cli/cli/v2/pkg/cmd/attestation/artifact/oci" + "github.com/cli/cli/v2/pkg/cmd/attestation/auth" + "github.com/cli/cli/v2/pkg/cmd/attestation/io" + "github.com/cli/cli/v2/pkg/cmd/attestation/verification" + "github.com/cli/cli/v2/pkg/cmdutil" + + "github.com/MakeNowJust/heredoc" + "github.com/spf13/cobra" +) + +func NewDownloadCmd(f *cmdutil.Factory, runF func(*Options) error) *cobra.Command { + opts := &Options{} + downloadCmd := &cobra.Command{ + Use: "download [ | oci://] [--owner | --repo]", + Args: cobra.ExactArgs(1), + Short: "Download an artifact's Sigstore bundle(s) for offline use", + Long: heredoc.Docf(` + Download an artifact's attestations, aka Sigstore bundle(s), for offline use. + + The command requires either: + * a file path to an artifact, or + * a container image URI (e.g. %[1]soci://%[1]s) + + (Note that if you provide an OCI URL, you must already be authenticated with + its container registry.) + + In addition, the command requires either: + * the %[1]s--owner%[1]s flag (e.g. --owner github), or + * the %[1]s--repo%[1]s flag (e.g. --repo github/example). + + The %[1]s--owner%[1]s flag value must match the name of the GitHub organization + that the artifact is associated with. + + The %[1]s--repo%[1]s flag value must match the name of the GitHub repository + that the artifact is associated with. + + Any associated Sigstore bundle(s) will be written to a file in the + current directory named after the artifact's digest. For example, if the + digest is "sha256:1234", the file will be named "sha256:1234.jsonl". + `, "`"), + Example: heredoc.Doc(` + # Download Sigstore bundle(s) for a local artifact associated with a GitHub organization + $ gh attestation download example.bin -o github + + # Download Sigstore bundle(s) for a local artifact associated with a GitHub repository + $ gh attestation download example.bin -R github/example + + # Download Sigstore bundle(s) for an OCI image associated with a GitHub organization + $ gh attestation download oci://example.com/foo/bar:latest -o github + `), + // PreRunE is used to validate flags before the command is run + // If an error is returned, its message will be printed to the terminal + // along with information about how use the command + PreRunE: func(cmd *cobra.Command, args []string) error { + // Create a logger for use throughout the download command + opts.Logger = io.NewHandler(f.IOStreams) + + // set the artifact path + opts.ArtifactPath = args[0] + + // check that the provided flags are valid + if err := opts.AreFlagsValid(); err != nil { + return err + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + hc, err := f.HttpClient() + if err != nil { + return err + } + opts.APIClient = api.NewLiveClient(hc, opts.Logger) + + opts.OCIClient = oci.NewLiveClient() + + opts.Store = NewLiveStore("") + + if err := auth.IsHostSupported(); err != nil { + return err + } + + if runF != nil { + return runF(opts) + } + + if err := runDownload(opts); err != nil { + return fmt.Errorf("Failed to download the artifact's bundle(s): %v", err) + } + return nil + }, + } + + downloadCmd.Flags().StringVarP(&opts.Owner, "owner", "o", "", "a GitHub organization to scope attestation lookup by") + downloadCmd.Flags().StringVarP(&opts.Repo, "repo", "R", "", "Repository name in the format /") + downloadCmd.MarkFlagsMutuallyExclusive("owner", "repo") + downloadCmd.MarkFlagsOneRequired("owner", "repo") + cmdutil.StringEnumFlag(downloadCmd, &opts.DigestAlgorithm, "digest-alg", "d", "sha256", []string{"sha256", "sha512"}, "The algorithm used to compute a digest of the artifact") + downloadCmd.Flags().IntVarP(&opts.Limit, "limit", "L", api.DefaultLimit, "Maximum number of attestations to fetch") + + return downloadCmd +} + +func runDownload(opts *Options) error { + artifact, err := artifact.NewDigestedArtifact(opts.OCIClient, opts.ArtifactPath, opts.DigestAlgorithm) + if err != nil { + return fmt.Errorf("failed to digest artifact: %v", err) + } + + opts.Logger.VerbosePrintf("Downloading trusted metadata for artifact %s\n\n", opts.ArtifactPath) + + c := verification.FetchAttestationsConfig{ + APIClient: opts.APIClient, + Digest: artifact.DigestWithAlg(), + Limit: opts.Limit, + Owner: opts.Owner, + Repo: opts.Repo, + } + attestations, err := verification.GetRemoteAttestations(c) + if err != nil { + if errors.Is(err, api.ErrNoAttestations{}) { + fmt.Fprintf(opts.Logger.IO.Out, "No attestations found for %s\n", opts.ArtifactPath) + return nil + } + return fmt.Errorf("failed to fetch attestations: %v", err) + } + + metadataFilePath, err := opts.Store.createMetadataFile(artifact.DigestWithAlg(), attestations) + if err != nil { + return fmt.Errorf("failed to write attestation: %v", err) + } + fmt.Fprintf(opts.Logger.IO.Out, "Wrote attestations to file %s.\nAny previous content has been overwritten\n\n", metadataFilePath) + + fmt.Fprint(opts.Logger.IO.Out, + opts.Logger.ColorScheme.Greenf( + "The trusted metadata is now available at %s\n", metadataFilePath, + ), + ) + + return nil +} diff --git a/pkg/cmd/attestation/download/download_test.go b/pkg/cmd/attestation/download/download_test.go new file mode 100644 index 000000000..0762a29da --- /dev/null +++ b/pkg/cmd/attestation/download/download_test.go @@ -0,0 +1,314 @@ +package download + +import ( + "bytes" + "fmt" + "net/http" + "strings" + "testing" + + "github.com/cli/cli/v2/pkg/cmd/attestation/api" + "github.com/cli/cli/v2/pkg/cmd/attestation/artifact" + "github.com/cli/cli/v2/pkg/cmd/attestation/artifact/oci" + "github.com/cli/cli/v2/pkg/cmd/attestation/io" + "github.com/cli/cli/v2/pkg/cmd/attestation/test" + "github.com/cli/cli/v2/pkg/cmdutil" + + "github.com/cli/cli/v2/pkg/httpmock" + "github.com/cli/cli/v2/pkg/iostreams" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var artifactPath = test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0.tgz") + +func TestNewDownloadCmd(t *testing.T) { + testIO, _, _, _ := iostreams.Test() + f := &cmdutil.Factory{ + IOStreams: testIO, + HttpClient: func() (*http.Client, error) { + reg := &httpmock.Registry{} + client := &http.Client{} + httpmock.ReplaceTripper(client, reg) + return client, nil + }, + } + + store := &LiveStore{ + outputPath: t.TempDir(), + } + + testcases := []struct { + name string + cli string + wants Options + wantsErr bool + }{ + { + name: "Invalid digest-alg flag", + cli: fmt.Sprintf("%s --owner sigstore --digest-alg sha384", artifactPath), + wants: Options{ + ArtifactPath: artifactPath, + APIClient: api.NewTestClient(), + OCIClient: oci.MockClient{}, + DigestAlgorithm: "sha384", + Owner: "sigstore", + Store: store, + Limit: 30, + }, + wantsErr: true, + }, + { + name: "Missing digest-alg flag", + cli: fmt.Sprintf("%s --owner sigstore", artifactPath), + wants: Options{ + ArtifactPath: artifactPath, + APIClient: api.NewTestClient(), + OCIClient: oci.MockClient{}, + DigestAlgorithm: "sha256", + Owner: "sigstore", + Store: store, + Limit: 30, + }, + wantsErr: false, + }, + { + name: "Missing owner and repo flags", + cli: artifactPath, + wants: Options{ + ArtifactPath: artifactPath, + APIClient: api.NewTestClient(), + OCIClient: oci.MockClient{}, + DigestAlgorithm: "sha256", + Owner: "sigstore", + Store: store, + Limit: 30, + }, + wantsErr: true, + }, + { + name: "Has both owner and repo flags", + cli: fmt.Sprintf("%s --owner sigstore --repo sigstore/sigstore-js", artifactPath), + wants: Options{ + ArtifactPath: artifactPath, + APIClient: api.NewTestClient(), + OCIClient: oci.MockClient{}, + DigestAlgorithm: "sha256", + Owner: "sigstore", + Store: store, + Repo: "sigstore/sigstore-js", + Limit: 30, + }, + wantsErr: true, + }, + { + name: "Uses default limit flag", + cli: fmt.Sprintf("%s --owner sigstore", artifactPath), + wants: Options{ + ArtifactPath: artifactPath, + APIClient: api.NewTestClient(), + OCIClient: oci.MockClient{}, + DigestAlgorithm: "sha256", + Owner: "sigstore", + Store: store, + Limit: 30, + }, + wantsErr: false, + }, + { + name: "Uses custom limit flag", + cli: fmt.Sprintf("%s --owner sigstore --limit 101", artifactPath), + wants: Options{ + ArtifactPath: artifactPath, + APIClient: api.NewTestClient(), + OCIClient: oci.MockClient{}, + DigestAlgorithm: "sha256", + Owner: "sigstore", + Store: store, + Limit: 101, + }, + wantsErr: false, + }, + { + name: "Uses invalid limit flag", + cli: fmt.Sprintf("%s --owner sigstore --limit 0", artifactPath), + wants: Options{ + ArtifactPath: artifactPath, + APIClient: api.NewTestClient(), + OCIClient: oci.MockClient{}, + DigestAlgorithm: "sha256", + Owner: "sigstore", + Store: store, + Limit: 0, + }, + wantsErr: true, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + var opts *Options + cmd := NewDownloadCmd(f, func(o *Options) error { + opts = o + return nil + }) + + argv := strings.Split(tc.cli, " ") + cmd.SetArgs(argv) + cmd.SetIn(&bytes.Buffer{}) + cmd.SetOut(&bytes.Buffer{}) + cmd.SetErr(&bytes.Buffer{}) + _, err := cmd.ExecuteC() + if tc.wantsErr { + assert.Error(t, err) + return + } + assert.NoError(t, err) + + assert.Equal(t, tc.wants.DigestAlgorithm, opts.DigestAlgorithm) + assert.Equal(t, tc.wants.Limit, opts.Limit) + assert.Equal(t, tc.wants.Owner, opts.Owner) + assert.Equal(t, tc.wants.Repo, opts.Repo) + assert.NotNil(t, opts.APIClient) + assert.NotNil(t, opts.OCIClient) + assert.NotNil(t, opts.Logger) + assert.NotNil(t, opts.Store) + }) + } +} + +func TestRunDownload(t *testing.T) { + tempDir := t.TempDir() + store := &LiveStore{ + outputPath: tempDir, + } + + baseOpts := Options{ + ArtifactPath: artifactPath, + APIClient: api.NewTestClient(), + OCIClient: oci.MockClient{}, + DigestAlgorithm: "sha512", + Owner: "sigstore", + Store: store, + Limit: 30, + Logger: io.NewTestHandler(), + } + + t.Run("fetch and store attestations successfully with owner", func(t *testing.T) { + err := runDownload(&baseOpts) + require.NoError(t, err) + + artifact, err := artifact.NewDigestedArtifact(baseOpts.OCIClient, baseOpts.ArtifactPath, baseOpts.DigestAlgorithm) + require.NoError(t, err) + + require.FileExists(t, fmt.Sprintf("%s/%s.jsonl", tempDir, artifact.DigestWithAlg())) + + actualLineCount, err := countLines(fmt.Sprintf("%s/%s.jsonl", tempDir, artifact.DigestWithAlg())) + require.NoError(t, err) + + expectedLineCount := 2 + require.Equal(t, expectedLineCount, actualLineCount) + }) + + t.Run("fetch and store attestations successfully with repo", func(t *testing.T) { + opts := baseOpts + opts.Owner = "" + opts.Repo = "sigstore/sigstore-js" + + err := runDownload(&opts) + require.NoError(t, err) + + artifact, err := artifact.NewDigestedArtifact(opts.OCIClient, opts.ArtifactPath, opts.DigestAlgorithm) + require.NoError(t, err) + + require.FileExists(t, fmt.Sprintf("%s/%s.jsonl", tempDir, artifact.DigestWithAlg())) + + actualLineCount, err := countLines(fmt.Sprintf("%s/%s.jsonl", tempDir, artifact.DigestWithAlg())) + require.NoError(t, err) + + expectedLineCount := 2 + require.Equal(t, expectedLineCount, actualLineCount) + }) + + t.Run("download OCI image attestations successfully", func(t *testing.T) { + opts := baseOpts + opts.ArtifactPath = "oci://ghcr.io/github/test" + + err := runDownload(&opts) + require.NoError(t, err) + + artifact, err := artifact.NewDigestedArtifact(opts.OCIClient, opts.ArtifactPath, opts.DigestAlgorithm) + require.NoError(t, err) + + require.FileExists(t, fmt.Sprintf("%s/%s.jsonl", tempDir, artifact.DigestWithAlg())) + + actualLineCount, err := countLines(fmt.Sprintf("%s/%s.jsonl", tempDir, artifact.DigestWithAlg())) + require.NoError(t, err) + + expectedLineCount := 2 + require.Equal(t, expectedLineCount, actualLineCount) + }) + + t.Run("cannot find artifact", func(t *testing.T) { + opts := baseOpts + opts.ArtifactPath = "../test/data/not-real.zip" + + err := runDownload(&opts) + require.Error(t, err) + }) + + t.Run("no attestations found", func(t *testing.T) { + opts := baseOpts + opts.APIClient = api.MockClient{ + OnGetByOwnerAndDigest: func(repo, digest string, limit int) ([]*api.Attestation, error) { + return nil, api.ErrNoAttestations{} + }, + } + + err := runDownload(&opts) + require.NoError(t, err) + + artifact, err := artifact.NewDigestedArtifact(opts.OCIClient, opts.ArtifactPath, opts.DigestAlgorithm) + require.NoError(t, err) + require.NoFileExists(t, artifact.DigestWithAlg()) + }) + + t.Run("failed to fetch attestations", func(t *testing.T) { + opts := baseOpts + opts.APIClient = api.MockClient{ + OnGetByOwnerAndDigest: func(repo, digest string, limit int) ([]*api.Attestation, error) { + return nil, fmt.Errorf("failed to fetch attestations") + }, + } + + err := runDownload(&opts) + require.Error(t, err) + }) + + t.Run("cannot download OCI artifact", func(t *testing.T) { + opts := baseOpts + opts.ArtifactPath = "oci://ghcr.io/github/test" + opts.OCIClient = oci.ReferenceFailClient{} + + err := runDownload(&opts) + require.Error(t, err) + require.ErrorContains(t, err, "failed to digest artifact") + }) + + t.Run("with missing API client", func(t *testing.T) { + customOpts := baseOpts + customOpts.APIClient = nil + require.Error(t, runDownload(&customOpts)) + }) + + t.Run("fail to write attestations to metadata file", func(t *testing.T) { + opts := baseOpts + opts.Store = &MockStore{ + OnCreateMetadataFile: OnCreateMetadataFileFailure, + } + + err := runDownload(&opts) + require.Error(t, err) + require.ErrorAs(t, err, &ErrAttestationFileCreation) + }) +} diff --git a/pkg/cmd/attestation/download/metadata.go b/pkg/cmd/attestation/download/metadata.go new file mode 100644 index 000000000..4096be001 --- /dev/null +++ b/pkg/cmd/attestation/download/metadata.go @@ -0,0 +1,70 @@ +package download + +import ( + "encoding/json" + "errors" + "fmt" + "os" + + "github.com/cli/cli/v2/pkg/cmd/attestation/api" +) + +var ErrAttestationFileCreation = fmt.Errorf("failed to write attestations to file") + +type MetadataStore interface { + createMetadataFile(artifactDigest string, attestationsResp []*api.Attestation) (string, error) +} + +type LiveStore struct { + outputPath string +} + +func (s *LiveStore) createJSONLinesFilePath(artifact string) string { + path := fmt.Sprintf("%s.jsonl", artifact) + if s.outputPath != "" { + return fmt.Sprintf("%s/%s", s.outputPath, path) + } + return path +} + +func (s *LiveStore) createMetadataFile(artifactDigest string, attestationsResp []*api.Attestation) (string, error) { + metadataFilePath := s.createJSONLinesFilePath(artifactDigest) + + f, err := os.Create(metadataFilePath) + if err != nil { + return "", errors.Join(ErrAttestationFileCreation, fmt.Errorf("failed to create file: %v", err)) + } + + for _, resp := range attestationsResp { + bundle := resp.Bundle + attBytes, err := json.Marshal(bundle) + if err != nil { + if err = f.Close(); err != nil { + return "", errors.Join(ErrAttestationFileCreation, fmt.Errorf("failed to close file while marshalling JSON: %v", err)) + } + return "", errors.Join(ErrAttestationFileCreation, fmt.Errorf("failed to marshall attestation to JSON while writing to file: %v", err)) + } + + withNewline := fmt.Sprintf("%s\n", attBytes) + _, err = f.Write([]byte(withNewline)) + if err != nil { + if err = f.Close(); err != nil { + return "", errors.Join(ErrAttestationFileCreation, fmt.Errorf("failed to close file while handling write error: %v", err)) + } + + return "", errors.Join(ErrAttestationFileCreation, fmt.Errorf("failed to write attestations: %v", err)) + } + } + + if err = f.Close(); err != nil { + return "", errors.Join(ErrAttestationFileCreation, fmt.Errorf("failed to close file after writing attestations: %v", err)) + } + + return metadataFilePath, nil +} + +func NewLiveStore(outputPath string) *LiveStore { + return &LiveStore{ + outputPath: outputPath, + } +} diff --git a/pkg/cmd/attestation/download/metadata_test.go b/pkg/cmd/attestation/download/metadata_test.go new file mode 100644 index 000000000..8ee3b2a45 --- /dev/null +++ b/pkg/cmd/attestation/download/metadata_test.go @@ -0,0 +1,87 @@ +package download + +import ( + "bufio" + "fmt" + "os" + "path" + "testing" + + "github.com/cli/cli/v2/pkg/cmd/attestation/api" + "github.com/cli/cli/v2/pkg/cmd/attestation/artifact" + "github.com/cli/cli/v2/pkg/cmd/attestation/artifact/oci" + + "github.com/stretchr/testify/require" +) + +type MockStore struct { + OnCreateMetadataFile func(artifactDigest string, attestationsResp []*api.Attestation) (string, error) +} + +func (s *MockStore) createMetadataFile(artifact string, attestationsResp []*api.Attestation) (string, error) { + return s.OnCreateMetadataFile(artifact, attestationsResp) +} + +func OnCreateMetadataFileFailure(artifactDigest string, attestationsResp []*api.Attestation) (string, error) { + return "", fmt.Errorf("failed to create trusted metadata file") +} + +func TestCreateJSONLinesFilePath(t *testing.T) { + tempDir := t.TempDir() + artifact, err := artifact.NewDigestedArtifact(oci.MockClient{}, "../test/data/sigstore-js-2.1.0.tgz", "sha512") + require.NoError(t, err) + + outputFileName := fmt.Sprintf("%s.jsonl", artifact.DigestWithAlg()) + + testCases := []struct { + name string + outputPath string + expected string + }{ + { + name: "with output path", + outputPath: tempDir, + expected: path.Join(tempDir, outputFileName), + }, + { + name: "with nested output path", + outputPath: path.Join(tempDir, "subdir"), + expected: path.Join(tempDir, "subdir", outputFileName), + }, + { + name: "with output path with beginning slash", + outputPath: path.Join("/", tempDir, "subdir"), + expected: path.Join("/", tempDir, "subdir", outputFileName), + }, + { + name: "without output path", + outputPath: "", + expected: outputFileName, + }, + } + + for _, tc := range testCases { + store := LiveStore{ + tc.outputPath, + } + + actualPath := store.createJSONLinesFilePath(artifact.DigestWithAlg()) + require.Equal(t, tc.expected, actualPath) + } +} + +func countLines(path string) (int, error) { + f, err := os.Open(path) + if err != nil { + return 0, err + } + defer f.Close() + + counter := 0 + scanner := bufio.NewScanner(f) + for scanner.Scan() { + counter += 1 + } + + return counter, nil +} diff --git a/pkg/cmd/attestation/download/options.go b/pkg/cmd/attestation/download/options.go new file mode 100644 index 000000000..6a64ecc30 --- /dev/null +++ b/pkg/cmd/attestation/download/options.go @@ -0,0 +1,35 @@ +package download + +import ( + "fmt" + + "github.com/cli/cli/v2/pkg/cmd/attestation/api" + "github.com/cli/cli/v2/pkg/cmd/attestation/artifact/oci" + "github.com/cli/cli/v2/pkg/cmd/attestation/io" +) + +const ( + minLimit = 1 + maxLimit = 1000 +) + +type Options struct { + APIClient api.Client + ArtifactPath string + DigestAlgorithm string + Logger *io.Handler + Limit int + Store MetadataStore + OCIClient oci.Client + Owner string + Repo string +} + +func (opts *Options) AreFlagsValid() error { + // Check that limit is between 1 and 1000 + if opts.Limit < minLimit || opts.Limit > maxLimit { + return fmt.Errorf("limit %d not allowed, must be between %d and %d", opts.Limit, minLimit, maxLimit) + } + + return nil +} diff --git a/pkg/cmd/attestation/download/options_test.go b/pkg/cmd/attestation/download/options_test.go new file mode 100644 index 000000000..800691d79 --- /dev/null +++ b/pkg/cmd/attestation/download/options_test.go @@ -0,0 +1,34 @@ +package download + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestAreFlagsValid(t *testing.T) { + tests := []struct { + name string + limit int + }{ + { + name: "Limit is too low", + limit: 0, + }, + { + name: "Limit is too high", + limit: 1001, + }, + } + for _, tc := range tests { + opts := Options{ + Limit: tc.limit, + } + + err := opts.AreFlagsValid() + require.Error(t, err) + expectedErrMsg := fmt.Sprintf("limit %d not allowed, must be between 1 and 1000", tc.limit) + require.ErrorContains(t, err, expectedErrMsg) + } +} diff --git a/pkg/cmd/attestation/inspect/bundle.go b/pkg/cmd/attestation/inspect/bundle.go new file mode 100644 index 000000000..283b2c14e --- /dev/null +++ b/pkg/cmd/attestation/inspect/bundle.go @@ -0,0 +1,128 @@ +package inspect + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/cli/cli/v2/pkg/cmd/attestation/api" + "github.com/cli/cli/v2/pkg/cmd/attestation/verification" +) + +type workflow struct { + Repository string `json:"repository"` +} + +type externalParameters struct { + Workflow workflow `json:"workflow"` +} + +type githubInfo struct { + RepositoryID string `json:"repository_id"` + RepositoryOwnerId string `json:"repository_owner_id"` +} + +type internalParameters struct { + GitHub githubInfo `json:"github"` +} + +type buildDefinition struct { + ExternalParameters externalParameters `json:"externalParameters"` + InternalParameters internalParameters `json:"internalParameters"` +} + +type metadata struct { + InvocationID string `json:"invocationId"` +} + +type runDetails struct { + Metadata metadata `json:"metadata"` +} + +// Predicate captures the predicate of a given attestation +type Predicate struct { + BuildDefinition buildDefinition `json:"buildDefinition"` + RunDetails runDetails `json:"runDetails"` +} + +// AttestationDetail captures attestation source details +// that will be returned by the inspect command +type AttestationDetail struct { + OrgName string `json:"orgName"` + OrgID string `json:"orgId"` + RepositoryName string `json:"repositoryName"` + RepositoryID string `json:"repositoryId"` + WorkflowID string `json:"workflowId"` +} + +func getOrgAndRepo(repoURL string) (string, string, error) { + after, found := strings.CutPrefix(repoURL, "https://github.com/") + if !found { + return "", "", fmt.Errorf("failed to get org and repo from %s", repoURL) + } + + parts := strings.Split(after, "/") + return parts[0], parts[1], nil +} + +func getAttestationDetail(attr api.Attestation) (AttestationDetail, error) { + envelope, err := attr.Bundle.Envelope() + if err != nil { + return AttestationDetail{}, fmt.Errorf("failed to get envelope from bundle: %v", err) + } + + statement, err := envelope.EnvelopeContent().Statement() + if err != nil { + return AttestationDetail{}, fmt.Errorf("failed to get statement from envelope: %v", err) + } + + var predicate Predicate + predicateJson, err := json.Marshal(statement.Predicate) + if err != nil { + return AttestationDetail{}, fmt.Errorf("failed to marshal predicate: %v", err) + } + + err = json.Unmarshal(predicateJson, &predicate) + if err != nil { + return AttestationDetail{}, fmt.Errorf("failed to unmarshal predicate: %v", err) + } + + org, repo, err := getOrgAndRepo(predicate.BuildDefinition.ExternalParameters.Workflow.Repository) + if err != nil { + return AttestationDetail{}, fmt.Errorf("failed to parse attestation content: %v", err) + } + + return AttestationDetail{ + OrgName: org, + OrgID: predicate.BuildDefinition.InternalParameters.GitHub.RepositoryOwnerId, + RepositoryName: repo, + RepositoryID: predicate.BuildDefinition.InternalParameters.GitHub.RepositoryID, + WorkflowID: predicate.RunDetails.Metadata.InvocationID, + }, nil +} + +func getDetailsAsSlice(results []*verification.AttestationProcessingResult) ([][]string, error) { + details := make([][]string, len(results)) + + for i, result := range results { + detail, err := getAttestationDetail(*result.Attestation) + if err != nil { + return nil, fmt.Errorf("failed to get attestation detail: %v", err) + } + details[i] = []string{detail.RepositoryName, detail.RepositoryID, detail.OrgName, detail.OrgID, detail.WorkflowID} + } + return details, nil +} + +func getAttestationDetails(results []*verification.AttestationProcessingResult) ([]AttestationDetail, error) { + details := make([]AttestationDetail, len(results)) + + for i, result := range results { + detail, err := getAttestationDetail(*result.Attestation) + if err != nil { + return nil, fmt.Errorf("failed to get attestation detail: %v", err) + } + details[i] = detail + } + return details, nil +} diff --git a/pkg/cmd/attestation/inspect/bundle_test.go b/pkg/cmd/attestation/inspect/bundle_test.go new file mode 100644 index 000000000..d7e9babd8 --- /dev/null +++ b/pkg/cmd/attestation/inspect/bundle_test.go @@ -0,0 +1,46 @@ +package inspect + +import ( + "testing" + + "github.com/cli/cli/v2/pkg/cmd/attestation/test" + "github.com/cli/cli/v2/pkg/cmd/attestation/verification" + + "github.com/stretchr/testify/require" +) + +func TestGetOrgAndRepo(t *testing.T) { + t.Run("with valid source URL", func(t *testing.T) { + sourceURL := "https://github.com/github/gh-attestation" + org, repo, err := getOrgAndRepo(sourceURL) + require.Nil(t, err) + require.Equal(t, "github", org) + require.Equal(t, "gh-attestation", repo) + }) + + t.Run("with invalid source URL", func(t *testing.T) { + sourceURL := "hub.com/github/gh-attestation" + org, repo, err := getOrgAndRepo(sourceURL) + require.Error(t, err) + require.Zero(t, org) + require.Zero(t, repo) + }) +} + +func TestGetAttestationDetail(t *testing.T) { + bundlePath := test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0-bundle.json") + + attestations, err := verification.GetLocalAttestations(bundlePath) + require.Len(t, attestations, 1) + require.NoError(t, err) + + attestation := attestations[0] + detail, err := getAttestationDetail(*attestation) + require.NoError(t, err) + + require.Equal(t, "sigstore", detail.OrgName) + require.Equal(t, "71096353", detail.OrgID) + require.Equal(t, "sigstore-js", detail.RepositoryName) + require.Equal(t, "495574555", detail.RepositoryID) + require.Equal(t, "https://github.com/sigstore/sigstore-js/actions/runs/6014488666/attempts/1", detail.WorkflowID) +} diff --git a/pkg/cmd/attestation/inspect/inspect.go b/pkg/cmd/attestation/inspect/inspect.go new file mode 100644 index 000000000..1b2105da4 --- /dev/null +++ b/pkg/cmd/attestation/inspect/inspect.go @@ -0,0 +1,162 @@ +package inspect + +import ( + "fmt" + + "github.com/cli/cli/v2/internal/tableprinter" + "github.com/cli/cli/v2/pkg/cmd/attestation/artifact" + "github.com/cli/cli/v2/pkg/cmd/attestation/artifact/oci" + "github.com/cli/cli/v2/pkg/cmd/attestation/auth" + "github.com/cli/cli/v2/pkg/cmd/attestation/io" + "github.com/cli/cli/v2/pkg/cmd/attestation/verification" + "github.com/cli/cli/v2/pkg/cmdutil" + + "github.com/MakeNowJust/heredoc" + "github.com/spf13/cobra" +) + +func NewInspectCmd(f *cmdutil.Factory, runF func(*Options) error) *cobra.Command { + opts := &Options{} + inspectCmd := &cobra.Command{ + Use: "inspect [ | oci://] --bundle ", + Args: cobra.ExactArgs(1), + Hidden: true, + Short: "Inspect a sigstore bundle", + Long: heredoc.Docf(` + Inspect a downloaded Sigstore bundle for a given artifact. + + The command requires either: + * a relative path to a local artifact, or + * a container image URI (e.g. %[1]soci://%[1]s) + + Note that if you provide an OCI URI for the artifact you must already + be authenticated with a container registry. + + The command also requires the %[1]s--bundle%[1]s flag, which provides a file + path to a previously downloaded Sigstore bundle. (See also the %[1]sdownload%[1]s + command). + + By default, the command will print information about the bundle in a table format. + If the %[1]s--json-result%[1]s flag is provided, the command will print the + information in JSON format. + `, "`"), + Example: heredoc.Doc(` + # Inspect a Sigstore bundle and print the results in table format + $ gh attestation inspect --bundle + + # Inspect a Sigstore bundle and print the results in JSON format + $ gh attestation inspect --bundle --json-result + + # Inspect a Sigsore bundle for an OCI artifact, and print the results in table format + $ gh attestation inspect oci:// --bundle + `), + PreRunE: func(cmd *cobra.Command, args []string) error { + // Create a logger for use throughout the inspect command + opts.Logger = io.NewHandler(f.IOStreams) + + // set the artifact path + opts.ArtifactPath = args[0] + + // Clean file path options + // opts.Clean() + + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + opts.OCIClient = oci.NewLiveClient() + + if err := auth.IsHostSupported(); err != nil { + return err + } + + if runF != nil { + return runF(opts) + } + + if err := runInspect(opts); err != nil { + return fmt.Errorf("Failed to inspect the artifact and bundle: %w", err) + } + return nil + }, + } + + inspectCmd.Flags().StringVarP(&opts.BundlePath, "bundle", "b", "", "Path to bundle on disk, either a single bundle in a JSON file or a JSON lines file with multiple bundles") + inspectCmd.MarkFlagRequired("bundle") //nolint:errcheck + cmdutil.StringEnumFlag(inspectCmd, &opts.DigestAlgorithm, "digest-alg", "d", "sha256", []string{"sha256", "sha512"}, "The algorithm used to compute a digest of the artifact") + cmdutil.AddFormatFlags(inspectCmd, &opts.exporter) + + return inspectCmd +} + +func runInspect(opts *Options) error { + artifact, err := artifact.NewDigestedArtifact(opts.OCIClient, opts.ArtifactPath, opts.DigestAlgorithm) + if err != nil { + return fmt.Errorf("failed to digest artifact: %s", err) + } + + opts.Logger.Printf("Verifying attestations for the artifact found at %s\n\n", artifact.URL) + + attestations, err := verification.GetLocalAttestations(opts.BundlePath) + if err != nil { + return fmt.Errorf("failed to read attestations for subject: %s", artifact.DigestWithAlg()) + } + + config := verification.SigstoreConfig{ + Logger: opts.Logger, + } + + policy, err := buildPolicy(*artifact) + if err != nil { + return fmt.Errorf("failed to build policy: %v", err) + } + + sigstore, err := verification.NewSigstoreVerifier(config, policy) + if err != nil { + return err + } + + res := sigstore.Verify(attestations) + if res.Error != nil { + return fmt.Errorf("at least one attestation failed to verify against Sigstore: %v", res.Error) + } + + opts.Logger.VerbosePrint(opts.Logger.ColorScheme.Green( + "Successfully verified all attestations against Sigstore!\n\n", + )) + + // If the user provides the --format=json flag, print the results in JSON format + if opts.exporter != nil { + details, err := getAttestationDetails(res.VerifyResults) + if err != nil { + return fmt.Errorf("failed to get attestation detail: %v", err) + } + + // print the results to the terminal as an array of JSON objects + if err = opts.exporter.Write(opts.Logger.IO, details); err != nil { + return fmt.Errorf("failed to write JSON output") + } + return nil + } + + // otherwise, print results in a table + details, err := getDetailsAsSlice(res.VerifyResults) + if err != nil { + return fmt.Errorf("failed to parse attestation details: %v", err) + } + + headers := []string{"Repo Name", "Repo ID", "Org Name", "Org ID", "Workflow ID"} + t := tableprinter.New(opts.Logger.IO, tableprinter.WithHeader(headers...)) + + for _, row := range details { + for _, field := range row { + t.AddField(field, tableprinter.WithTruncate(nil)) + } + t.EndRow() + } + + if err = t.Render(); err != nil { + return fmt.Errorf("failed to print output: %v", err) + } + + return nil +} diff --git a/pkg/cmd/attestation/inspect/inspect_test.go b/pkg/cmd/attestation/inspect/inspect_test.go new file mode 100644 index 000000000..e42bb2620 --- /dev/null +++ b/pkg/cmd/attestation/inspect/inspect_test.go @@ -0,0 +1,179 @@ +package inspect + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "strings" + "testing" + + "github.com/cli/cli/v2/pkg/cmd/attestation/artifact/oci" + "github.com/cli/cli/v2/pkg/cmd/attestation/io" + "github.com/cli/cli/v2/pkg/cmd/attestation/test" + "github.com/cli/cli/v2/pkg/cmdutil" + + "github.com/cli/cli/v2/pkg/httpmock" + "github.com/cli/cli/v2/pkg/iostreams" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const ( + SigstoreSanValue = "https://github.com/sigstore/sigstore-js/.github/workflows/release.yml@refs/heads/main" + SigstoreSanRegex = "^https://github.com/sigstore/sigstore-js/" +) + +var ( + artifactPath = test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0.tgz") + bundlePath = test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0-bundle.json") +) + +func TestNewInspectCmd(t *testing.T) { + testIO, _, _, _ := iostreams.Test() + f := &cmdutil.Factory{ + IOStreams: testIO, + HttpClient: func() (*http.Client, error) { + reg := &httpmock.Registry{} + client := &http.Client{} + httpmock.ReplaceTripper(client, reg) + return client, nil + }, + } + + testcases := []struct { + name string + cli string + wants Options + wantsErr bool + wantsExporter bool + }{ + { + name: "Invalid digest-alg flag", + cli: fmt.Sprintf("%s --bundle %s --digest-alg sha384", artifactPath, bundlePath), + wants: Options{ + ArtifactPath: artifactPath, + BundlePath: bundlePath, + DigestAlgorithm: "sha384", + OCIClient: oci.MockClient{}, + }, + wantsErr: true, + }, + { + name: "Use default digest-alg value", + cli: fmt.Sprintf("%s --bundle %s", artifactPath, bundlePath), + wants: Options{ + ArtifactPath: artifactPath, + BundlePath: bundlePath, + DigestAlgorithm: "sha256", + OCIClient: oci.MockClient{}, + }, + wantsErr: false, + }, + { + name: "Use custom digest-alg value", + cli: fmt.Sprintf("%s --bundle %s --digest-alg sha512", artifactPath, bundlePath), + wants: Options{ + ArtifactPath: artifactPath, + BundlePath: bundlePath, + DigestAlgorithm: "sha512", + OCIClient: oci.MockClient{}, + }, + wantsErr: false, + }, + { + name: "Missing bundle flag", + cli: artifactPath, + wants: Options{ + ArtifactPath: artifactPath, + DigestAlgorithm: "sha256", + OCIClient: oci.MockClient{}, + }, + wantsErr: true, + }, + { + name: "Prints output in JSON format", + cli: fmt.Sprintf("%s --bundle %s --format json", artifactPath, bundlePath), + wants: Options{ + ArtifactPath: artifactPath, + BundlePath: bundlePath, + DigestAlgorithm: "sha256", + OCIClient: oci.MockClient{}, + }, + wantsExporter: true, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + var opts *Options + cmd := NewInspectCmd(f, func(o *Options) error { + opts = o + return nil + }) + + argv := strings.Split(tc.cli, " ") + cmd.SetArgs(argv) + cmd.SetIn(&bytes.Buffer{}) + cmd.SetOut(&bytes.Buffer{}) + cmd.SetErr(&bytes.Buffer{}) + _, err := cmd.ExecuteC() + if tc.wantsErr { + assert.Error(t, err) + return + } + assert.NoError(t, err) + + assert.Equal(t, tc.wants.ArtifactPath, opts.ArtifactPath) + assert.Equal(t, tc.wants.BundlePath, opts.BundlePath) + assert.Equal(t, tc.wants.DigestAlgorithm, opts.DigestAlgorithm) + assert.NotNil(t, opts.OCIClient) + assert.NotNil(t, opts.Logger) + assert.Equal(t, tc.wantsExporter, opts.exporter != nil) + }) + } +} + +func TestRunInspect(t *testing.T) { + opts := Options{ + ArtifactPath: artifactPath, + BundlePath: bundlePath, + DigestAlgorithm: "sha512", + Logger: io.NewTestHandler(), + OCIClient: oci.MockClient{}, + } + + t.Run("with valid artifact and bundle", func(t *testing.T) { + require.Nil(t, runInspect(&opts)) + }) + + t.Run("with missing artifact path", func(t *testing.T) { + customOpts := opts + customOpts.ArtifactPath = test.NormalizeRelativePath("../test/data/non-existent-artifact.zip") + require.Error(t, runInspect(&customOpts)) + }) + + t.Run("with missing bundle path", func(t *testing.T) { + customOpts := opts + customOpts.BundlePath = test.NormalizeRelativePath("../test/data/non-existent-sigstoreBundle.json") + require.Error(t, runInspect(&customOpts)) + }) +} + +func TestJSONOutput(t *testing.T) { + testIO, _, out, _ := iostreams.Test() + opts := Options{ + ArtifactPath: artifactPath, + BundlePath: bundlePath, + DigestAlgorithm: "sha512", + Logger: io.NewHandler(testIO), + OCIClient: oci.MockClient{}, + exporter: cmdutil.NewJSONExporter(), + } + require.Nil(t, runInspect(&opts)) + + var target []AttestationDetail + err := json.Unmarshal(out.Bytes(), &target) + require.NoError(t, err) +} diff --git a/pkg/cmd/attestation/inspect/options.go b/pkg/cmd/attestation/inspect/options.go new file mode 100644 index 000000000..56199e06b --- /dev/null +++ b/pkg/cmd/attestation/inspect/options.go @@ -0,0 +1,24 @@ +package inspect + +import ( + "path/filepath" + + "github.com/cli/cli/v2/pkg/cmd/attestation/artifact/oci" + "github.com/cli/cli/v2/pkg/cmd/attestation/io" + "github.com/cli/cli/v2/pkg/cmdutil" +) + +// Options captures the options for the inspect command +type Options struct { + ArtifactPath string + BundlePath string + DigestAlgorithm string + Logger *io.Handler + OCIClient oci.Client + exporter cmdutil.Exporter +} + +// Clean cleans the file path option values +func (opts *Options) Clean() { + opts.BundlePath = filepath.Clean(opts.BundlePath) +} diff --git a/pkg/cmd/attestation/inspect/policy.go b/pkg/cmd/attestation/inspect/policy.go new file mode 100644 index 000000000..49313d35a --- /dev/null +++ b/pkg/cmd/attestation/inspect/policy.go @@ -0,0 +1,18 @@ +package inspect + +import ( + "github.com/cli/cli/v2/pkg/cmd/attestation/artifact" + "github.com/cli/cli/v2/pkg/cmd/attestation/verification" + + sigstoreVerify "github.com/sigstore/sigstore-go/pkg/verify" +) + +func buildPolicy(a artifact.DigestedArtifact) (sigstoreVerify.PolicyBuilder, error) { + artifactDigestPolicyOption, err := verification.BuildDigestPolicyOption(a) + if err != nil { + return sigstoreVerify.PolicyBuilder{}, err + } + + policy := sigstoreVerify.NewPolicy(artifactDigestPolicyOption, sigstoreVerify.WithoutIdentitiesUnsafe()) + return policy, nil +} diff --git a/pkg/cmd/attestation/io/handler.go b/pkg/cmd/attestation/io/handler.go new file mode 100644 index 000000000..b41d4ddbf --- /dev/null +++ b/pkg/cmd/attestation/io/handler.go @@ -0,0 +1,61 @@ +package io + +import ( + "fmt" + + "github.com/cli/cli/v2/pkg/iostreams" + "github.com/cli/cli/v2/utils" +) + +type Handler struct { + ColorScheme *iostreams.ColorScheme + IO *iostreams.IOStreams + debugEnabled bool +} + +func NewHandler(io *iostreams.IOStreams) *Handler { + enabled, _ := utils.IsDebugEnabled() + + return &Handler{ + ColorScheme: io.ColorScheme(), + IO: io, + debugEnabled: enabled, + } +} + +func NewTestHandler() *Handler { + testIO, _, _, _ := iostreams.Test() + return NewHandler(testIO) +} + +// Printf writes the formatted arguments to the stderr writer. +func (h *Handler) Printf(f string, v ...interface{}) (int, error) { + if !h.IO.IsStdoutTTY() { + return 0, nil + } + return fmt.Fprintf(h.IO.ErrOut, f, v...) +} + +// Println writes the arguments to the stderr writer with a newline at the end. +func (h *Handler) Println(v ...interface{}) (int, error) { + if !h.IO.IsStdoutTTY() { + return 0, nil + } + return fmt.Fprintln(h.IO.ErrOut, v...) +} + +func (h *Handler) VerbosePrint(msg string) (int, error) { + if !h.debugEnabled || !h.IO.IsStdoutTTY() { + return 0, nil + } + + return fmt.Fprintln(h.IO.ErrOut, msg) +} + +func (h *Handler) VerbosePrintf(f string, v ...interface{}) (int, error) { + if !h.debugEnabled || !h.IO.IsStdoutTTY() { + return 0, nil + } + + return fmt.Fprintf(h.IO.ErrOut, f, v...) +} diff --git a/pkg/cmd/attestation/test/data/sigstore-js-2.1.0-bundle.json b/pkg/cmd/attestation/test/data/sigstore-js-2.1.0-bundle.json new file mode 100644 index 000000000..d91176e09 --- /dev/null +++ b/pkg/cmd/attestation/test/data/sigstore-js-2.1.0-bundle.json @@ -0,0 +1,61 @@ +{ + "mediaType": "application/vnd.dev.sigstore.bundle+json;version=0.1", + "verificationMaterial": { + "x509CertificateChain": { + "certificates": [ + { + "rawBytes": "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" + } + ] + }, + "tlogEntries": [ + { + "logIndex": "33351527", + "logId": { + "keyId": "wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0=" + }, + "kindVersion": { + "kind": "intoto", + "version": "0.0.2" + }, + "integratedTime": "1693323623", + "inclusionPromise": { + "signedEntryTimestamp": "MEYCIQDhWvNSLvnq5ZS3qTIgC7K2uQFeA0g8FEEjNo1UQxeubAIhALDrD1uIiUkk3tQNp/4gKT/9j8zEyyxi9Ti+qaD8q2vI" + }, + "inclusionProof": { + "logIndex": "29188096", + "rootHash": "fbEijQTFeiQCldZqez/u/WcrNPk4nxXI5ihofHYjFUA=", + "treeSize": "29188099", + "hashes": [ + "z7VKeAC2d2x18Vtxt7n40GS3gtc1xfRwjjxxzpy/Trw=", + "/Kd8ZuCLZ7MukSwpjaSgxKFl5X2vdHWk6/qpNrkiUJo=", + "vvkKs7IShUI15nVb9c5olPgBnL9r4uP7+d5KzV0hSjs=", + "Fg4p0WJZw8Lghrpxkx1SXgzfroTeIIrEgEbfgr9IdXY=", + "bH8NahqE+hQ58Qxhg0V5fYusFQ1xsaQ/rK6slWVRT5k=", + "HplNgZ3/afEHq52zcfL2s1AKisOYDdMCWK1jOu1tGyw=", + "uOPuC2YDmwZe989ZN3Lgh5CKMXh9HETrSNgf0jV0WkM=", + "eVsvWKnEZ1+Xo3Ba15DfEiIhhmlrEaIeb+VfYmx8KhY=", + "uLuBRins5nkqq2rqd17R27pQTUF+xetttC6MsmlUzd0=", + "jRUq4D8O+FI47Wbw96s7yHCu4qzWUxpIVfxQEeprDmc=", + "rXEsmEJN4PEoTU8US4qVtdIsGB1MCiRlGOepoiC99kM=" + ], + "checkpoint": { + "envelope": "rekor.sigstore.dev - 2605736670972794746\n29188099\nfbEijQTFeiQCldZqez/u/WcrNPk4nxXI5ihofHYjFUA=\nTimestamp: 1693323623528968756\n\n— rekor.sigstore.dev wNI9ajBEAiAK0YTbxRlhOZeeP+844Y+W7iz+hsFIF8x2NYsmuaxifAIgYUFSurlKwN7j5jCwpqSVrkbouQoYIYlyWU9Om16svmI=\n" + } + }, + "canonicalizedBody": "eyJhcGlWZXJzaW9uIjoiMC4wLjIiLCJraW5kIjoiaW50b3RvIiwic3BlYyI6eyJjb250ZW50Ijp7ImVudmVsb3BlIjp7InBheWxvYWRUeXBlIjoiYXBwbGljYXRpb24vdm5kLmluLXRvdG8ranNvbiIsInNpZ25hdHVyZXMiOlt7InB1YmxpY0tleSI6IkxTMHRMUzFDUlVkSlRpQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENrMUpTVWQwUkVORFFtcDFaMEYzU1VKQlowbFZRMHBNYVhCVGREQTVTMHhHWXpCS1dXWjFSSEpUWVc0dkwweHpkME5uV1VsTGIxcEplbW93UlVGM1RYY0tUbnBGVmsxQ1RVZEJNVlZGUTJoTlRXTXliRzVqTTFKMlkyMVZkVnBIVmpKTlVqUjNTRUZaUkZaUlVVUkZlRlo2WVZka2VtUkhPWGxhVXpGd1ltNVNiQXBqYlRGc1drZHNhR1JIVlhkSWFHTk9UV3BOZDA5RVNUVk5WRlV3VFVSSmVsZG9ZMDVOYWsxM1QwUkpOVTFVVlRGTlJFbDZWMnBCUVUxR2EzZEZkMWxJQ2t0dldrbDZhakJEUVZGWlNVdHZXa2w2YWpCRVFWRmpSRkZuUVVWUVptMDJURkJZVVdWS1ZFTTRPVlZQY1dsT1YyMXVXbGxIYlZnMFZETnBURnBIYVRBS1JWWTBZbVpQYjAwNE5raDZZVGswV0hGNWRYZDRRVzlYY0VOUVpXTkdRMFZpUVdVNGJESmtaeTlsY2pOUE9VeEZSbkZQUTBKV2IzZG5aMVpYVFVFMFJ3cEJNVlZrUkhkRlFpOTNVVVZCZDBsSVowUkJWRUpuVGxaSVUxVkZSRVJCUzBKblozSkNaMFZHUWxGalJFRjZRV1JDWjA1V1NGRTBSVVpuVVZWbGNYQkRDbGhJY2pOd1kxVmhURE5GUmt0U0swdHpiWFZMVVhGdmQwaDNXVVJXVWpCcVFrSm5kMFp2UVZVek9WQndlakZaYTBWYVlqVnhUbXB3UzBaWGFYaHBORmtLV2tRNGQxbDNXVVJXVWpCU1FWRklMMEpHYTNkV05GcFdZVWhTTUdOSVRUWk1lVGx1WVZoU2IyUlhTWFZaTWpsMFRETk9jRm96VGpCaU0wcHNURE5PY0FwYU0wNHdZak5LYkV4WGNIcE1lVFZ1WVZoU2IyUlhTWFprTWpsNVlUSmFjMkl6WkhwTU0wcHNZa2RXYUdNeVZYVmxWekZ6VVVoS2JGcHVUWFpoUjFab0NscElUWFppVjBad1ltcEJOVUpuYjNKQ1owVkZRVmxQTDAxQlJVSkNRM1J2WkVoU2QyTjZiM1pNTTFKMllUSldkVXh0Um1wa1IyeDJZbTVOZFZveWJEQUtZVWhXYVdSWVRteGpiVTUyWW01U2JHSnVVWFZaTWpsMFRVSkpSME5wYzBkQlVWRkNaemM0ZDBGUlNVVkNTRUl4WXpKbmQwNW5XVXRMZDFsQ1FrRkhSQXAyZWtGQ1FYZFJiMDFxV210TlZGa3hUVlJOZWs5RVdtMWFiVVpvVG5wcmQxbHFSbXBOZWtwdFQxUkpNMDVVVVRCYWFrVjZUV3BLYkU1RVJUVk9SRUZXQ2tKbmIzSkNaMFZGUVZsUEwwMUJSVVZDUVdSVFdsZDRiRmxZVG14TlEwbEhRMmx6UjBGUlVVSm5OemgzUVZGVlJVWklUbkJhTTA0d1lqTktiRXd6VG5BS1dqTk9NR0l6U214TVYzQjZUVUl3UjBOcGMwZEJVVkZDWnpjNGQwRlJXVVZFTTBwc1dtNU5kbUZIVm1oYVNFMTJZbGRHY0dKcVFUZENaMjl5UW1kRlJRcEJXVTh2VFVGRlNVSkRNRTFMTW1nd1pFaENlazlwT0haa1J6bHlXbGMwZFZsWFRqQmhWemwxWTNrMWJtRllVbTlrVjBveFl6SldlVmt5T1hWa1IxWjFDbVJETldwaU1qQjNXbEZaUzB0M1dVSkNRVWRFZG5wQlFrTlJVbGhFUmxadlpFaFNkMk42YjNaTU1tUndaRWRvTVZscE5XcGlNakIyWXpKc2JtTXpVbllLWTIxVmRtTXliRzVqTTFKMlkyMVZkR0Z1VFhaTWJXUndaRWRvTVZscE9UTmlNMHB5V20xNGRtUXpUWFpqYlZaeldsZEdlbHBUTlRWaVYzaEJZMjFXYlFwamVUbHZXbGRHYTJONU9YUlpWMngxVFVSblIwTnBjMGRCVVZGQ1p6YzRkMEZSYjBWTFozZHZUV3BhYTAxVVdURk5WRTE2VDBSYWJWcHRSbWhPZW10M0NsbHFSbXBOZWtwdFQxUkpNMDVVVVRCYWFrVjZUV3BLYkU1RVJUVk9SRUZrUW1kdmNrSm5SVVZCV1U4dlRVRkZURUpCT0UxRVYyUndaRWRvTVZscE1XOEtZak5PTUZwWFVYZE9kMWxMUzNkWlFrSkJSMFIyZWtGQ1JFRlJjRVJEWkc5a1NGSjNZM3B2ZGt3eVpIQmtSMmd4V1drMWFtSXlNSFpqTW14dVl6TlNkZ3BqYlZWMll6SnNibU16VW5aamJWVjBZVzVOZDA5QldVdExkMWxDUWtGSFJIWjZRVUpFVVZGeFJFTm5lVTV0VVhoT2FsVjRUWHBOTkU1dFdtMVpWMFV6Q2s5VVFtbE5WMDE2VFcxWk5VMXFZekZPUkZKdFRWUk5lVTF0VlRCTlZHc3dUVUk0UjBOcGMwZEJVVkZDWnpjNGQwRlJORVZGVVhkUVkyMVdiV041T1c4S1dsZEdhMk41T1hSWlYyeDFUVUpyUjBOcGMwZEJVVkZDWnpjNGQwRlJPRVZEZDNkS1RrUnJNVTVVWXpCT1ZGVXhUVU56UjBOcGMwZEJVVkZDWnpjNGR3cEJVa0ZGU0ZGM1ltRklVakJqU0UwMlRIazVibUZZVW05a1YwbDFXVEk1ZEV3elRuQmFNMDR3WWpOS2JFMUNaMGREYVhOSFFWRlJRbWMzT0hkQlVrVkZDa05uZDBsT2VrVjNUMVJaZWs1VVRYZGFVVmxMUzNkWlFrSkJSMFIyZWtGQ1JXZFNXRVJHVm05a1NGSjNZM3B2ZGt3eVpIQmtSMmd4V1drMWFtSXlNSFlLWXpKc2JtTXpVblpqYlZWMll6SnNibU16VW5aamJWVjBZVzVOZGt4dFpIQmtSMmd4V1drNU0ySXpTbkphYlhoMlpETk5kbU50Vm5OYVYwWjZXbE0xTlFwaVYzaEJZMjFXYldONU9XOWFWMFpyWTNrNWRGbFhiSFZOUkdkSFEybHpSMEZSVVVKbk56aDNRVkpOUlV0bmQyOU5hbHByVFZSWk1VMVVUWHBQUkZwdENscHRSbWhPZW10M1dXcEdhazE2U20xUFZFa3pUbFJSTUZwcVJYcE5ha3BzVGtSRk5VNUVRVlZDWjI5eVFtZEZSVUZaVHk5TlFVVlZRa0ZaVFVKSVFqRUtZekpuZDFkbldVdExkMWxDUWtGSFJIWjZRVUpHVVZKTlJFVndiMlJJVW5kamVtOTJUREprY0dSSGFERlphVFZxWWpJd2RtTXliRzVqTTFKMlkyMVZkZ3BqTW14dVl6TlNkbU50VlhSaGJrMTJXVmRPTUdGWE9YVmplVGw1WkZjMWVreDZXWGROVkZFd1QwUm5NazVxV1haWldGSXdXbGN4ZDJSSVRYWk5WRUZYQ2tKbmIzSkNaMFZGUVZsUEwwMUJSVmRDUVdkTlFtNUNNVmx0ZUhCWmVrTkNhV2RaUzB0M1dVSkNRVWhYWlZGSlJVRm5VamhDU0c5QlpVRkNNa0ZPTURrS1RVZHlSM2g0UlhsWmVHdGxTRXBzYms1M1MybFRiRFkwTTJwNWRDODBaVXRqYjBGMlMyVTJUMEZCUVVKcGEwaDZLM1ozUVVGQlVVUkJSV04zVWxGSlp3cGFSVzg0WXpCbFExcElSV2cwZFhwNlNucEdlamxVSzBWbVUxUk9WSFJDTWtaSlNERTRkbGh3YTA5elEwbFJSRVV4VFZSMGFUbFNiMFJ1VWs4elUwVlVDakZhYTJGa05rWnZWSGd2YXpaNmRGRmpkMGxFVUcxdVVuaFVRVXRDWjJkeGFHdHFUMUJSVVVSQmQwNXVRVVJDYTBGcVFrSXdObVp0VGxoNE5sUnZZVU1LYkVabk1tdFBlRzVtVEVkbmNuWnZVak5HTlVkcVJIUjJSRUpDT0cwNVUxZFJUbnBNTWpFeGFsbHRVeTluSzFsaVlubFZRMDFESzJGa05tcEpTeXRsWmdwbE5GaFBTV3hvVEdOWGVHVmFZa0owVFdwTFUzSlFlRzF0TkdwU00wSkdVVkZQUWxJck9ISXlOME5wYjNsb2VHOVRjWFpNZFhjOVBRb3RMUzB0TFVWT1JDQkRSVkpVU1VaSlEwRlVSUzB0TFMwdCIsInNpZyI6IlRVVlJRMGxEWVhSb2JsUkljMlJtV25GSWNUTnBXRXhxVFdVd1ZGVTViRXhaYmxJeGVtazNNM0JSZFZobU5VdElRV2xDTWpOS2FDdG5VMll4VVVWRGJrRlRSMlZ3TW1VeVRVZHVVRmhwYjFWTVZqUjJRMGxUU2tKdWEwcGFaejA5In1dfSwiaGFzaCI6eyJhbGdvcml0aG0iOiJzaGEyNTYiLCJ2YWx1ZSI6IjFiZDg4ZTA1NGY2OGE3ZDE3MzkzNjcyYjAzZmExOGZkNjRmMGRjZDVmY2M1NDAzNWMxOWZlNjQwMWNmMmNmNWUifSwicGF5bG9hZEhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI4Y2ViNGFiODEyNzczMTQ3M2E5ZWM4MTE0MGNiNjg0OWNmOGU5NzBjZGEzMmJhZWYwOTlkZjQ4YmEzMjY0NDQyIn19fX0=" + } + ], + "timestampVerificationData": null + }, + "dsseEnvelope": { + "payload": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoicGtnOm5wbS9zaWdzdG9yZUAyLjEuMCIsImRpZ2VzdCI6eyJzaGE1MTIiOiI5MGYyMjNmOTkyZTRjODhkZDA2OGNkMmE1ZmM1N2Y5ZDJiMzA3OTgzNDNkZDZlMzhmMjljMjQwZTA0YmEwOTBlZjgzMWY4NDQ5MDg0N2M0ZTgyYjkyMzJjNzhlOGEyNTg0NjNiMWU1NWMwZjc0NjlmNzMwMjY1MDA4ZmE2NjMzZiJ9fV0sInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjEiLCJwcmVkaWNhdGUiOnsiYnVpbGREZWZpbml0aW9uIjp7ImJ1aWxkVHlwZSI6Imh0dHBzOi8vc2xzYS1mcmFtZXdvcmsuZ2l0aHViLmlvL2dpdGh1Yi1hY3Rpb25zLWJ1aWxkdHlwZXMvd29ya2Zsb3cvdjEiLCJleHRlcm5hbFBhcmFtZXRlcnMiOnsid29ya2Zsb3ciOnsicmVmIjoicmVmcy9oZWFkcy9tYWluIiwicmVwb3NpdG9yeSI6Imh0dHBzOi8vZ2l0aHViLmNvbS9zaWdzdG9yZS9zaWdzdG9yZS1qcyIsInBhdGgiOiIuZ2l0aHViL3dvcmtmbG93cy9yZWxlYXNlLnltbCJ9fSwiaW50ZXJuYWxQYXJhbWV0ZXJzIjp7ImdpdGh1YiI6eyJldmVudF9uYW1lIjoicHVzaCIsInJlcG9zaXRvcnlfaWQiOiI0OTU1NzQ1NTUiLCJyZXBvc2l0b3J5X293bmVyX2lkIjoiNzEwOTYzNTMifX0sInJlc29sdmVkRGVwZW5kZW5jaWVzIjpbeyJ1cmkiOiJnaXQraHR0cHM6Ly9naXRodWIuY29tL3NpZ3N0b3JlL3NpZ3N0b3JlLWpzQHJlZnMvaGVhZHMvbWFpbiIsImRpZ2VzdCI6eyJnaXRDb21taXQiOiIyNmQxNjUxMzM4NmZmYWE3OTBiMWMzMmY5Mjc1NDRmMTMyMmU0MTk0In19XX0sInJ1bkRldGFpbHMiOnsiYnVpbGRlciI6eyJpZCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hY3Rpb25zL3J1bm5lci9naXRodWItaG9zdGVkIn0sIm1ldGFkYXRhIjp7Imludm9jYXRpb25JZCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9zaWdzdG9yZS9zaWdzdG9yZS1qcy9hY3Rpb25zL3J1bnMvNjAxNDQ4ODY2Ni9hdHRlbXB0cy8xIn19fX0=", + "payloadType": "application/vnd.in-toto+json", + "signatures": [ + { + "sig": "MEQCICathnTHsdfZqHq3iXLjMe0TU9lLYnR1zi73pQuXf5KHAiB23Jh+gSf1QECnASGep2e2MGnPXioULV4vCISJBnkJZg==", + "keyid": "" + } + ] + } + } diff --git a/pkg/cmd/attestation/test/data/sigstore-js-2.1.0.tgz b/pkg/cmd/attestation/test/data/sigstore-js-2.1.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..390b823fd12e377849e020aa2915f7ed9e77f924 GIT binary patch literal 37137 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 literal 0 HcmV?d00001 diff --git a/pkg/cmd/attestation/test/data/sigstore-js-2.1.0_with_2_bundles.jsonl b/pkg/cmd/attestation/test/data/sigstore-js-2.1.0_with_2_bundles.jsonl new file mode 100644 index 000000000..a4aca34d0 --- /dev/null +++ b/pkg/cmd/attestation/test/data/sigstore-js-2.1.0_with_2_bundles.jsonl @@ -0,0 +1,2 @@ +{"mediaType":"application/vnd.dev.sigstore.bundle+json;version=0.1","verificationMaterial":{"x509CertificateChain":{"certificates":[{"rawBytes":"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"}]},"tlogEntries":[{"logIndex":"33351527","logId":{"keyId":"wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="},"kindVersion":{"kind":"intoto","version":"0.0.2"},"integratedTime":"1693323623","inclusionPromise":{"signedEntryTimestamp":"MEYCIQDhWvNSLvnq5ZS3qTIgC7K2uQFeA0g8FEEjNo1UQxeubAIhALDrD1uIiUkk3tQNp/4gKT/9j8zEyyxi9Ti+qaD8q2vI"},"inclusionProof":{"logIndex":"29188096","rootHash":"fbEijQTFeiQCldZqez/u/WcrNPk4nxXI5ihofHYjFUA=","treeSize":"29188099","hashes":["z7VKeAC2d2x18Vtxt7n40GS3gtc1xfRwjjxxzpy/Trw=","/Kd8ZuCLZ7MukSwpjaSgxKFl5X2vdHWk6/qpNrkiUJo=","vvkKs7IShUI15nVb9c5olPgBnL9r4uP7+d5KzV0hSjs=","Fg4p0WJZw8Lghrpxkx1SXgzfroTeIIrEgEbfgr9IdXY=","bH8NahqE+hQ58Qxhg0V5fYusFQ1xsaQ/rK6slWVRT5k=","HplNgZ3/afEHq52zcfL2s1AKisOYDdMCWK1jOu1tGyw=","uOPuC2YDmwZe989ZN3Lgh5CKMXh9HETrSNgf0jV0WkM=","eVsvWKnEZ1+Xo3Ba15DfEiIhhmlrEaIeb+VfYmx8KhY=","uLuBRins5nkqq2rqd17R27pQTUF+xetttC6MsmlUzd0=","jRUq4D8O+FI47Wbw96s7yHCu4qzWUxpIVfxQEeprDmc=","rXEsmEJN4PEoTU8US4qVtdIsGB1MCiRlGOepoiC99kM="],"checkpoint":{"envelope":"rekor.sigstore.dev - 2605736670972794746\n29188099\nfbEijQTFeiQCldZqez/u/WcrNPk4nxXI5ihofHYjFUA=\nTimestamp: 1693323623528968756\n\n— rekor.sigstore.dev wNI9ajBEAiAK0YTbxRlhOZeeP+844Y+W7iz+hsFIF8x2NYsmuaxifAIgYUFSurlKwN7j5jCwpqSVrkbouQoYIYlyWU9Om16svmI=\n"}},"canonicalizedBody":"eyJhcGlWZXJzaW9uIjoiMC4wLjIiLCJraW5kIjoiaW50b3RvIiwic3BlYyI6eyJjb250ZW50Ijp7ImVudmVsb3BlIjp7InBheWxvYWRUeXBlIjoiYXBwbGljYXRpb24vdm5kLmluLXRvdG8ranNvbiIsInNpZ25hdHVyZXMiOlt7InB1YmxpY0tleSI6IkxTMHRMUzFDUlVkSlRpQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENrMUpTVWQwUkVORFFtcDFaMEYzU1VKQlowbFZRMHBNYVhCVGREQTVTMHhHWXpCS1dXWjFSSEpUWVc0dkwweHpkME5uV1VsTGIxcEplbW93UlVGM1RYY0tUbnBGVmsxQ1RVZEJNVlZGUTJoTlRXTXliRzVqTTFKMlkyMVZkVnBIVmpKTlVqUjNTRUZaUkZaUlVVUkZlRlo2WVZka2VtUkhPWGxhVXpGd1ltNVNiQXBqYlRGc1drZHNhR1JIVlhkSWFHTk9UV3BOZDA5RVNUVk5WRlV3VFVSSmVsZG9ZMDVOYWsxM1QwUkpOVTFVVlRGTlJFbDZWMnBCUVUxR2EzZEZkMWxJQ2t0dldrbDZhakJEUVZGWlNVdHZXa2w2YWpCRVFWRmpSRkZuUVVWUVptMDJURkJZVVdWS1ZFTTRPVlZQY1dsT1YyMXVXbGxIYlZnMFZETnBURnBIYVRBS1JWWTBZbVpQYjAwNE5raDZZVGswV0hGNWRYZDRRVzlYY0VOUVpXTkdRMFZpUVdVNGJESmtaeTlsY2pOUE9VeEZSbkZQUTBKV2IzZG5aMVpYVFVFMFJ3cEJNVlZrUkhkRlFpOTNVVVZCZDBsSVowUkJWRUpuVGxaSVUxVkZSRVJCUzBKblozSkNaMFZHUWxGalJFRjZRV1JDWjA1V1NGRTBSVVpuVVZWbGNYQkRDbGhJY2pOd1kxVmhURE5GUmt0U0swdHpiWFZMVVhGdmQwaDNXVVJXVWpCcVFrSm5kMFp2UVZVek9WQndlakZaYTBWYVlqVnhUbXB3UzBaWGFYaHBORmtLV2tRNGQxbDNXVVJXVWpCU1FWRklMMEpHYTNkV05GcFdZVWhTTUdOSVRUWk1lVGx1WVZoU2IyUlhTWFZaTWpsMFRETk9jRm96VGpCaU0wcHNURE5PY0FwYU0wNHdZak5LYkV4WGNIcE1lVFZ1WVZoU2IyUlhTWFprTWpsNVlUSmFjMkl6WkhwTU0wcHNZa2RXYUdNeVZYVmxWekZ6VVVoS2JGcHVUWFpoUjFab0NscElUWFppVjBad1ltcEJOVUpuYjNKQ1owVkZRVmxQTDAxQlJVSkNRM1J2WkVoU2QyTjZiM1pNTTFKMllUSldkVXh0Um1wa1IyeDJZbTVOZFZveWJEQUtZVWhXYVdSWVRteGpiVTUyWW01U2JHSnVVWFZaTWpsMFRVSkpSME5wYzBkQlVWRkNaemM0ZDBGUlNVVkNTRUl4WXpKbmQwNW5XVXRMZDFsQ1FrRkhSQXAyZWtGQ1FYZFJiMDFxV210TlZGa3hUVlJOZWs5RVdtMWFiVVpvVG5wcmQxbHFSbXBOZWtwdFQxUkpNMDVVVVRCYWFrVjZUV3BLYkU1RVJUVk9SRUZXQ2tKbmIzSkNaMFZGUVZsUEwwMUJSVVZDUVdSVFdsZDRiRmxZVG14TlEwbEhRMmx6UjBGUlVVSm5OemgzUVZGVlJVWklUbkJhTTA0d1lqTktiRXd6VG5BS1dqTk9NR0l6U214TVYzQjZUVUl3UjBOcGMwZEJVVkZDWnpjNGQwRlJXVVZFTTBwc1dtNU5kbUZIVm1oYVNFMTJZbGRHY0dKcVFUZENaMjl5UW1kRlJRcEJXVTh2VFVGRlNVSkRNRTFMTW1nd1pFaENlazlwT0haa1J6bHlXbGMwZFZsWFRqQmhWemwxWTNrMWJtRllVbTlrVjBveFl6SldlVmt5T1hWa1IxWjFDbVJETldwaU1qQjNXbEZaUzB0M1dVSkNRVWRFZG5wQlFrTlJVbGhFUmxadlpFaFNkMk42YjNaTU1tUndaRWRvTVZscE5XcGlNakIyWXpKc2JtTXpVbllLWTIxVmRtTXliRzVqTTFKMlkyMVZkR0Z1VFhaTWJXUndaRWRvTVZscE9UTmlNMHB5V20xNGRtUXpUWFpqYlZaeldsZEdlbHBUTlRWaVYzaEJZMjFXYlFwamVUbHZXbGRHYTJONU9YUlpWMngxVFVSblIwTnBjMGRCVVZGQ1p6YzRkMEZSYjBWTFozZHZUV3BhYTAxVVdURk5WRTE2VDBSYWJWcHRSbWhPZW10M0NsbHFSbXBOZWtwdFQxUkpNMDVVVVRCYWFrVjZUV3BLYkU1RVJUVk9SRUZrUW1kdmNrSm5SVVZCV1U4dlRVRkZURUpCT0UxRVYyUndaRWRvTVZscE1XOEtZak5PTUZwWFVYZE9kMWxMUzNkWlFrSkJSMFIyZWtGQ1JFRlJjRVJEWkc5a1NGSjNZM3B2ZGt3eVpIQmtSMmd4V1drMWFtSXlNSFpqTW14dVl6TlNkZ3BqYlZWMll6SnNibU16VW5aamJWVjBZVzVOZDA5QldVdExkMWxDUWtGSFJIWjZRVUpFVVZGeFJFTm5lVTV0VVhoT2FsVjRUWHBOTkU1dFdtMVpWMFV6Q2s5VVFtbE5WMDE2VFcxWk5VMXFZekZPUkZKdFRWUk5lVTF0VlRCTlZHc3dUVUk0UjBOcGMwZEJVVkZDWnpjNGQwRlJORVZGVVhkUVkyMVdiV041T1c4S1dsZEdhMk41T1hSWlYyeDFUVUpyUjBOcGMwZEJVVkZDWnpjNGQwRlJPRVZEZDNkS1RrUnJNVTVVWXpCT1ZGVXhUVU56UjBOcGMwZEJVVkZDWnpjNGR3cEJVa0ZGU0ZGM1ltRklVakJqU0UwMlRIazVibUZZVW05a1YwbDFXVEk1ZEV3elRuQmFNMDR3WWpOS2JFMUNaMGREYVhOSFFWRlJRbWMzT0hkQlVrVkZDa05uZDBsT2VrVjNUMVJaZWs1VVRYZGFVVmxMUzNkWlFrSkJSMFIyZWtGQ1JXZFNXRVJHVm05a1NGSjNZM3B2ZGt3eVpIQmtSMmd4V1drMWFtSXlNSFlLWXpKc2JtTXpVblpqYlZWMll6SnNibU16VW5aamJWVjBZVzVOZGt4dFpIQmtSMmd4V1drNU0ySXpTbkphYlhoMlpETk5kbU50Vm5OYVYwWjZXbE0xTlFwaVYzaEJZMjFXYldONU9XOWFWMFpyWTNrNWRGbFhiSFZOUkdkSFEybHpSMEZSVVVKbk56aDNRVkpOUlV0bmQyOU5hbHByVFZSWk1VMVVUWHBQUkZwdENscHRSbWhPZW10M1dXcEdhazE2U20xUFZFa3pUbFJSTUZwcVJYcE5ha3BzVGtSRk5VNUVRVlZDWjI5eVFtZEZSVUZaVHk5TlFVVlZRa0ZaVFVKSVFqRUtZekpuZDFkbldVdExkMWxDUWtGSFJIWjZRVUpHVVZKTlJFVndiMlJJVW5kamVtOTJUREprY0dSSGFERlphVFZxWWpJd2RtTXliRzVqTTFKMlkyMVZkZ3BqTW14dVl6TlNkbU50VlhSaGJrMTJXVmRPTUdGWE9YVmplVGw1WkZjMWVreDZXWGROVkZFd1QwUm5NazVxV1haWldGSXdXbGN4ZDJSSVRYWk5WRUZYQ2tKbmIzSkNaMFZGUVZsUEwwMUJSVmRDUVdkTlFtNUNNVmx0ZUhCWmVrTkNhV2RaUzB0M1dVSkNRVWhYWlZGSlJVRm5VamhDU0c5QlpVRkNNa0ZPTURrS1RVZHlSM2g0UlhsWmVHdGxTRXBzYms1M1MybFRiRFkwTTJwNWRDODBaVXRqYjBGMlMyVTJUMEZCUVVKcGEwaDZLM1ozUVVGQlVVUkJSV04zVWxGSlp3cGFSVzg0WXpCbFExcElSV2cwZFhwNlNucEdlamxVSzBWbVUxUk9WSFJDTWtaSlNERTRkbGh3YTA5elEwbFJSRVV4VFZSMGFUbFNiMFJ1VWs4elUwVlVDakZhYTJGa05rWnZWSGd2YXpaNmRGRmpkMGxFVUcxdVVuaFVRVXRDWjJkeGFHdHFUMUJSVVVSQmQwNXVRVVJDYTBGcVFrSXdObVp0VGxoNE5sUnZZVU1LYkVabk1tdFBlRzVtVEVkbmNuWnZVak5HTlVkcVJIUjJSRUpDT0cwNVUxZFJUbnBNTWpFeGFsbHRVeTluSzFsaVlubFZRMDFESzJGa05tcEpTeXRsWmdwbE5GaFBTV3hvVEdOWGVHVmFZa0owVFdwTFUzSlFlRzF0TkdwU00wSkdVVkZQUWxJck9ISXlOME5wYjNsb2VHOVRjWFpNZFhjOVBRb3RMUzB0TFVWT1JDQkRSVkpVU1VaSlEwRlVSUzB0TFMwdCIsInNpZyI6IlRVVlJRMGxEWVhSb2JsUkljMlJtV25GSWNUTnBXRXhxVFdVd1ZGVTViRXhaYmxJeGVtazNNM0JSZFZobU5VdElRV2xDTWpOS2FDdG5VMll4VVVWRGJrRlRSMlZ3TW1VeVRVZHVVRmhwYjFWTVZqUjJRMGxUU2tKdWEwcGFaejA5In1dfSwiaGFzaCI6eyJhbGdvcml0aG0iOiJzaGEyNTYiLCJ2YWx1ZSI6IjFiZDg4ZTA1NGY2OGE3ZDE3MzkzNjcyYjAzZmExOGZkNjRmMGRjZDVmY2M1NDAzNWMxOWZlNjQwMWNmMmNmNWUifSwicGF5bG9hZEhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI4Y2ViNGFiODEyNzczMTQ3M2E5ZWM4MTE0MGNiNjg0OWNmOGU5NzBjZGEzMmJhZWYwOTlkZjQ4YmEzMjY0NDQyIn19fX0="}],"timestampVerificationData":null},"dsseEnvelope":{"payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoicGtnOm5wbS9zaWdzdG9yZUAyLjEuMCIsImRpZ2VzdCI6eyJzaGE1MTIiOiI5MGYyMjNmOTkyZTRjODhkZDA2OGNkMmE1ZmM1N2Y5ZDJiMzA3OTgzNDNkZDZlMzhmMjljMjQwZTA0YmEwOTBlZjgzMWY4NDQ5MDg0N2M0ZTgyYjkyMzJjNzhlOGEyNTg0NjNiMWU1NWMwZjc0NjlmNzMwMjY1MDA4ZmE2NjMzZiJ9fV0sInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjEiLCJwcmVkaWNhdGUiOnsiYnVpbGREZWZpbml0aW9uIjp7ImJ1aWxkVHlwZSI6Imh0dHBzOi8vc2xzYS1mcmFtZXdvcmsuZ2l0aHViLmlvL2dpdGh1Yi1hY3Rpb25zLWJ1aWxkdHlwZXMvd29ya2Zsb3cvdjEiLCJleHRlcm5hbFBhcmFtZXRlcnMiOnsid29ya2Zsb3ciOnsicmVmIjoicmVmcy9oZWFkcy9tYWluIiwicmVwb3NpdG9yeSI6Imh0dHBzOi8vZ2l0aHViLmNvbS9zaWdzdG9yZS9zaWdzdG9yZS1qcyIsInBhdGgiOiIuZ2l0aHViL3dvcmtmbG93cy9yZWxlYXNlLnltbCJ9fSwiaW50ZXJuYWxQYXJhbWV0ZXJzIjp7ImdpdGh1YiI6eyJldmVudF9uYW1lIjoicHVzaCIsInJlcG9zaXRvcnlfaWQiOiI0OTU1NzQ1NTUiLCJyZXBvc2l0b3J5X293bmVyX2lkIjoiNzEwOTYzNTMifX0sInJlc29sdmVkRGVwZW5kZW5jaWVzIjpbeyJ1cmkiOiJnaXQraHR0cHM6Ly9naXRodWIuY29tL3NpZ3N0b3JlL3NpZ3N0b3JlLWpzQHJlZnMvaGVhZHMvbWFpbiIsImRpZ2VzdCI6eyJnaXRDb21taXQiOiIyNmQxNjUxMzM4NmZmYWE3OTBiMWMzMmY5Mjc1NDRmMTMyMmU0MTk0In19XX0sInJ1bkRldGFpbHMiOnsiYnVpbGRlciI6eyJpZCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hY3Rpb25zL3J1bm5lci9naXRodWItaG9zdGVkIn0sIm1ldGFkYXRhIjp7Imludm9jYXRpb25JZCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9zaWdzdG9yZS9zaWdzdG9yZS1qcy9hY3Rpb25zL3J1bnMvNjAxNDQ4ODY2Ni9hdHRlbXB0cy8xIn19fX0=","payloadType":"application/vnd.in-toto+json","signatures":[{"sig":"MEQCICathnTHsdfZqHq3iXLjMe0TU9lLYnR1zi73pQuXf5KHAiB23Jh+gSf1QECnASGep2e2MGnPXioULV4vCISJBnkJZg==","keyid":""}]}} +{"mediaType":"application/vnd.dev.sigstore.bundle+json;version=0.1","verificationMaterial":{"x509CertificateChain":{"certificates":[{"rawBytes":"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"}]},"tlogEntries":[{"logIndex":"33351527","logId":{"keyId":"wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="},"kindVersion":{"kind":"intoto","version":"0.0.2"},"integratedTime":"1693323623","inclusionPromise":{"signedEntryTimestamp":"MEYCIQDhWvNSLvnq5ZS3qTIgC7K2uQFeA0g8FEEjNo1UQxeubAIhALDrD1uIiUkk3tQNp/4gKT/9j8zEyyxi9Ti+qaD8q2vI"},"inclusionProof":{"logIndex":"29188096","rootHash":"fbEijQTFeiQCldZqez/u/WcrNPk4nxXI5ihofHYjFUA=","treeSize":"29188099","hashes":["z7VKeAC2d2x18Vtxt7n40GS3gtc1xfRwjjxxzpy/Trw=","/Kd8ZuCLZ7MukSwpjaSgxKFl5X2vdHWk6/qpNrkiUJo=","vvkKs7IShUI15nVb9c5olPgBnL9r4uP7+d5KzV0hSjs=","Fg4p0WJZw8Lghrpxkx1SXgzfroTeIIrEgEbfgr9IdXY=","bH8NahqE+hQ58Qxhg0V5fYusFQ1xsaQ/rK6slWVRT5k=","HplNgZ3/afEHq52zcfL2s1AKisOYDdMCWK1jOu1tGyw=","uOPuC2YDmwZe989ZN3Lgh5CKMXh9HETrSNgf0jV0WkM=","eVsvWKnEZ1+Xo3Ba15DfEiIhhmlrEaIeb+VfYmx8KhY=","uLuBRins5nkqq2rqd17R27pQTUF+xetttC6MsmlUzd0=","jRUq4D8O+FI47Wbw96s7yHCu4qzWUxpIVfxQEeprDmc=","rXEsmEJN4PEoTU8US4qVtdIsGB1MCiRlGOepoiC99kM="],"checkpoint":{"envelope":"rekor.sigstore.dev - 2605736670972794746\n29188099\nfbEijQTFeiQCldZqez/u/WcrNPk4nxXI5ihofHYjFUA=\nTimestamp: 1693323623528968756\n\n— rekor.sigstore.dev wNI9ajBEAiAK0YTbxRlhOZeeP+844Y+W7iz+hsFIF8x2NYsmuaxifAIgYUFSurlKwN7j5jCwpqSVrkbouQoYIYlyWU9Om16svmI=\n"}},"canonicalizedBody":"eyJhcGlWZXJzaW9uIjoiMC4wLjIiLCJraW5kIjoiaW50b3RvIiwic3BlYyI6eyJjb250ZW50Ijp7ImVudmVsb3BlIjp7InBheWxvYWRUeXBlIjoiYXBwbGljYXRpb24vdm5kLmluLXRvdG8ranNvbiIsInNpZ25hdHVyZXMiOlt7InB1YmxpY0tleSI6IkxTMHRMUzFDUlVkSlRpQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENrMUpTVWQwUkVORFFtcDFaMEYzU1VKQlowbFZRMHBNYVhCVGREQTVTMHhHWXpCS1dXWjFSSEpUWVc0dkwweHpkME5uV1VsTGIxcEplbW93UlVGM1RYY0tUbnBGVmsxQ1RVZEJNVlZGUTJoTlRXTXliRzVqTTFKMlkyMVZkVnBIVmpKTlVqUjNTRUZaUkZaUlVVUkZlRlo2WVZka2VtUkhPWGxhVXpGd1ltNVNiQXBqYlRGc1drZHNhR1JIVlhkSWFHTk9UV3BOZDA5RVNUVk5WRlV3VFVSSmVsZG9ZMDVOYWsxM1QwUkpOVTFVVlRGTlJFbDZWMnBCUVUxR2EzZEZkMWxJQ2t0dldrbDZhakJEUVZGWlNVdHZXa2w2YWpCRVFWRmpSRkZuUVVWUVptMDJURkJZVVdWS1ZFTTRPVlZQY1dsT1YyMXVXbGxIYlZnMFZETnBURnBIYVRBS1JWWTBZbVpQYjAwNE5raDZZVGswV0hGNWRYZDRRVzlYY0VOUVpXTkdRMFZpUVdVNGJESmtaeTlsY2pOUE9VeEZSbkZQUTBKV2IzZG5aMVpYVFVFMFJ3cEJNVlZrUkhkRlFpOTNVVVZCZDBsSVowUkJWRUpuVGxaSVUxVkZSRVJCUzBKblozSkNaMFZHUWxGalJFRjZRV1JDWjA1V1NGRTBSVVpuVVZWbGNYQkRDbGhJY2pOd1kxVmhURE5GUmt0U0swdHpiWFZMVVhGdmQwaDNXVVJXVWpCcVFrSm5kMFp2UVZVek9WQndlakZaYTBWYVlqVnhUbXB3UzBaWGFYaHBORmtLV2tRNGQxbDNXVVJXVWpCU1FWRklMMEpHYTNkV05GcFdZVWhTTUdOSVRUWk1lVGx1WVZoU2IyUlhTWFZaTWpsMFRETk9jRm96VGpCaU0wcHNURE5PY0FwYU0wNHdZak5LYkV4WGNIcE1lVFZ1WVZoU2IyUlhTWFprTWpsNVlUSmFjMkl6WkhwTU0wcHNZa2RXYUdNeVZYVmxWekZ6VVVoS2JGcHVUWFpoUjFab0NscElUWFppVjBad1ltcEJOVUpuYjNKQ1owVkZRVmxQTDAxQlJVSkNRM1J2WkVoU2QyTjZiM1pNTTFKMllUSldkVXh0Um1wa1IyeDJZbTVOZFZveWJEQUtZVWhXYVdSWVRteGpiVTUyWW01U2JHSnVVWFZaTWpsMFRVSkpSME5wYzBkQlVWRkNaemM0ZDBGUlNVVkNTRUl4WXpKbmQwNW5XVXRMZDFsQ1FrRkhSQXAyZWtGQ1FYZFJiMDFxV210TlZGa3hUVlJOZWs5RVdtMWFiVVpvVG5wcmQxbHFSbXBOZWtwdFQxUkpNMDVVVVRCYWFrVjZUV3BLYkU1RVJUVk9SRUZXQ2tKbmIzSkNaMFZGUVZsUEwwMUJSVVZDUVdSVFdsZDRiRmxZVG14TlEwbEhRMmx6UjBGUlVVSm5OemgzUVZGVlJVWklUbkJhTTA0d1lqTktiRXd6VG5BS1dqTk9NR0l6U214TVYzQjZUVUl3UjBOcGMwZEJVVkZDWnpjNGQwRlJXVVZFTTBwc1dtNU5kbUZIVm1oYVNFMTJZbGRHY0dKcVFUZENaMjl5UW1kRlJRcEJXVTh2VFVGRlNVSkRNRTFMTW1nd1pFaENlazlwT0haa1J6bHlXbGMwZFZsWFRqQmhWemwxWTNrMWJtRllVbTlrVjBveFl6SldlVmt5T1hWa1IxWjFDbVJETldwaU1qQjNXbEZaUzB0M1dVSkNRVWRFZG5wQlFrTlJVbGhFUmxadlpFaFNkMk42YjNaTU1tUndaRWRvTVZscE5XcGlNakIyWXpKc2JtTXpVbllLWTIxVmRtTXliRzVqTTFKMlkyMVZkR0Z1VFhaTWJXUndaRWRvTVZscE9UTmlNMHB5V20xNGRtUXpUWFpqYlZaeldsZEdlbHBUTlRWaVYzaEJZMjFXYlFwamVUbHZXbGRHYTJONU9YUlpWMngxVFVSblIwTnBjMGRCVVZGQ1p6YzRkMEZSYjBWTFozZHZUV3BhYTAxVVdURk5WRTE2VDBSYWJWcHRSbWhPZW10M0NsbHFSbXBOZWtwdFQxUkpNMDVVVVRCYWFrVjZUV3BLYkU1RVJUVk9SRUZrUW1kdmNrSm5SVVZCV1U4dlRVRkZURUpCT0UxRVYyUndaRWRvTVZscE1XOEtZak5PTUZwWFVYZE9kMWxMUzNkWlFrSkJSMFIyZWtGQ1JFRlJjRVJEWkc5a1NGSjNZM3B2ZGt3eVpIQmtSMmd4V1drMWFtSXlNSFpqTW14dVl6TlNkZ3BqYlZWMll6SnNibU16VW5aamJWVjBZVzVOZDA5QldVdExkMWxDUWtGSFJIWjZRVUpFVVZGeFJFTm5lVTV0VVhoT2FsVjRUWHBOTkU1dFdtMVpWMFV6Q2s5VVFtbE5WMDE2VFcxWk5VMXFZekZPUkZKdFRWUk5lVTF0VlRCTlZHc3dUVUk0UjBOcGMwZEJVVkZDWnpjNGQwRlJORVZGVVhkUVkyMVdiV041T1c4S1dsZEdhMk41T1hSWlYyeDFUVUpyUjBOcGMwZEJVVkZDWnpjNGQwRlJPRVZEZDNkS1RrUnJNVTVVWXpCT1ZGVXhUVU56UjBOcGMwZEJVVkZDWnpjNGR3cEJVa0ZGU0ZGM1ltRklVakJqU0UwMlRIazVibUZZVW05a1YwbDFXVEk1ZEV3elRuQmFNMDR3WWpOS2JFMUNaMGREYVhOSFFWRlJRbWMzT0hkQlVrVkZDa05uZDBsT2VrVjNUMVJaZWs1VVRYZGFVVmxMUzNkWlFrSkJSMFIyZWtGQ1JXZFNXRVJHVm05a1NGSjNZM3B2ZGt3eVpIQmtSMmd4V1drMWFtSXlNSFlLWXpKc2JtTXpVblpqYlZWMll6SnNibU16VW5aamJWVjBZVzVOZGt4dFpIQmtSMmd4V1drNU0ySXpTbkphYlhoMlpETk5kbU50Vm5OYVYwWjZXbE0xTlFwaVYzaEJZMjFXYldONU9XOWFWMFpyWTNrNWRGbFhiSFZOUkdkSFEybHpSMEZSVVVKbk56aDNRVkpOUlV0bmQyOU5hbHByVFZSWk1VMVVUWHBQUkZwdENscHRSbWhPZW10M1dXcEdhazE2U20xUFZFa3pUbFJSTUZwcVJYcE5ha3BzVGtSRk5VNUVRVlZDWjI5eVFtZEZSVUZaVHk5TlFVVlZRa0ZaVFVKSVFqRUtZekpuZDFkbldVdExkMWxDUWtGSFJIWjZRVUpHVVZKTlJFVndiMlJJVW5kamVtOTJUREprY0dSSGFERlphVFZxWWpJd2RtTXliRzVqTTFKMlkyMVZkZ3BqTW14dVl6TlNkbU50VlhSaGJrMTJXVmRPTUdGWE9YVmplVGw1WkZjMWVreDZXWGROVkZFd1QwUm5NazVxV1haWldGSXdXbGN4ZDJSSVRYWk5WRUZYQ2tKbmIzSkNaMFZGUVZsUEwwMUJSVmRDUVdkTlFtNUNNVmx0ZUhCWmVrTkNhV2RaUzB0M1dVSkNRVWhYWlZGSlJVRm5VamhDU0c5QlpVRkNNa0ZPTURrS1RVZHlSM2g0UlhsWmVHdGxTRXBzYms1M1MybFRiRFkwTTJwNWRDODBaVXRqYjBGMlMyVTJUMEZCUVVKcGEwaDZLM1ozUVVGQlVVUkJSV04zVWxGSlp3cGFSVzg0WXpCbFExcElSV2cwZFhwNlNucEdlamxVSzBWbVUxUk9WSFJDTWtaSlNERTRkbGh3YTA5elEwbFJSRVV4VFZSMGFUbFNiMFJ1VWs4elUwVlVDakZhYTJGa05rWnZWSGd2YXpaNmRGRmpkMGxFVUcxdVVuaFVRVXRDWjJkeGFHdHFUMUJSVVVSQmQwNXVRVVJDYTBGcVFrSXdObVp0VGxoNE5sUnZZVU1LYkVabk1tdFBlRzVtVEVkbmNuWnZVak5HTlVkcVJIUjJSRUpDT0cwNVUxZFJUbnBNTWpFeGFsbHRVeTluSzFsaVlubFZRMDFESzJGa05tcEpTeXRsWmdwbE5GaFBTV3hvVEdOWGVHVmFZa0owVFdwTFUzSlFlRzF0TkdwU00wSkdVVkZQUWxJck9ISXlOME5wYjNsb2VHOVRjWFpNZFhjOVBRb3RMUzB0TFVWT1JDQkRSVkpVU1VaSlEwRlVSUzB0TFMwdCIsInNpZyI6IlRVVlJRMGxEWVhSb2JsUkljMlJtV25GSWNUTnBXRXhxVFdVd1ZGVTViRXhaYmxJeGVtazNNM0JSZFZobU5VdElRV2xDTWpOS2FDdG5VMll4VVVWRGJrRlRSMlZ3TW1VeVRVZHVVRmhwYjFWTVZqUjJRMGxUU2tKdWEwcGFaejA5In1dfSwiaGFzaCI6eyJhbGdvcml0aG0iOiJzaGEyNTYiLCJ2YWx1ZSI6IjFiZDg4ZTA1NGY2OGE3ZDE3MzkzNjcyYjAzZmExOGZkNjRmMGRjZDVmY2M1NDAzNWMxOWZlNjQwMWNmMmNmNWUifSwicGF5bG9hZEhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI4Y2ViNGFiODEyNzczMTQ3M2E5ZWM4MTE0MGNiNjg0OWNmOGU5NzBjZGEzMmJhZWYwOTlkZjQ4YmEzMjY0NDQyIn19fX0="}],"timestampVerificationData":null},"dsseEnvelope":{"payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoicGtnOm5wbS9zaWdzdG9yZUAyLjEuMCIsImRpZ2VzdCI6eyJzaGE1MTIiOiI5MGYyMjNmOTkyZTRjODhkZDA2OGNkMmE1ZmM1N2Y5ZDJiMzA3OTgzNDNkZDZlMzhmMjljMjQwZTA0YmEwOTBlZjgzMWY4NDQ5MDg0N2M0ZTgyYjkyMzJjNzhlOGEyNTg0NjNiMWU1NWMwZjc0NjlmNzMwMjY1MDA4ZmE2NjMzZiJ9fV0sInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjEiLCJwcmVkaWNhdGUiOnsiYnVpbGREZWZpbml0aW9uIjp7ImJ1aWxkVHlwZSI6Imh0dHBzOi8vc2xzYS1mcmFtZXdvcmsuZ2l0aHViLmlvL2dpdGh1Yi1hY3Rpb25zLWJ1aWxkdHlwZXMvd29ya2Zsb3cvdjEiLCJleHRlcm5hbFBhcmFtZXRlcnMiOnsid29ya2Zsb3ciOnsicmVmIjoicmVmcy9oZWFkcy9tYWluIiwicmVwb3NpdG9yeSI6Imh0dHBzOi8vZ2l0aHViLmNvbS9zaWdzdG9yZS9zaWdzdG9yZS1qcyIsInBhdGgiOiIuZ2l0aHViL3dvcmtmbG93cy9yZWxlYXNlLnltbCJ9fSwiaW50ZXJuYWxQYXJhbWV0ZXJzIjp7ImdpdGh1YiI6eyJldmVudF9uYW1lIjoicHVzaCIsInJlcG9zaXRvcnlfaWQiOiI0OTU1NzQ1NTUiLCJyZXBvc2l0b3J5X293bmVyX2lkIjoiNzEwOTYzNTMifX0sInJlc29sdmVkRGVwZW5kZW5jaWVzIjpbeyJ1cmkiOiJnaXQraHR0cHM6Ly9naXRodWIuY29tL3NpZ3N0b3JlL3NpZ3N0b3JlLWpzQHJlZnMvaGVhZHMvbWFpbiIsImRpZ2VzdCI6eyJnaXRDb21taXQiOiIyNmQxNjUxMzM4NmZmYWE3OTBiMWMzMmY5Mjc1NDRmMTMyMmU0MTk0In19XX0sInJ1bkRldGFpbHMiOnsiYnVpbGRlciI6eyJpZCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hY3Rpb25zL3J1bm5lci9naXRodWItaG9zdGVkIn0sIm1ldGFkYXRhIjp7Imludm9jYXRpb25JZCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9zaWdzdG9yZS9zaWdzdG9yZS1qcy9hY3Rpb25zL3J1bnMvNjAxNDQ4ODY2Ni9hdHRlbXB0cy8xIn19fX0=","payloadType":"application/vnd.in-toto+json","signatures":[{"sig":"MEQCICathnTHsdfZqHq3iXLjMe0TU9lLYnR1zi73pQuXf5KHAiB23Jh+gSf1QECnASGep2e2MGnPXioULV4vCISJBnkJZg==","keyid":""}]}} diff --git a/pkg/cmd/attestation/test/data/sigstoreBundle-invalid-signature.json b/pkg/cmd/attestation/test/data/sigstoreBundle-invalid-signature.json new file mode 100644 index 000000000..b1275cebc --- /dev/null +++ b/pkg/cmd/attestation/test/data/sigstoreBundle-invalid-signature.json @@ -0,0 +1,48 @@ +{ + "mediaType": "application/vnd.dev.sigstore.bundle+json;version=0.1", + "verificationMaterial": { + "tlogEntries": [ + { + "logIndex": "6751924", + "logId": { + "keyId": "wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0=" + }, + "kindVersion": { + "kind": "intoto", + "version": "0.0.2" + }, + "integratedTime": "1667948287", + "inclusionPromise": { + "signedEntryTimestamp": "MEQCIEzguFRaGzOpMw9JJGUfqSJQ11qlzpcyVCkZfZYPwpLCAiBzdU4LnjtVKYCfyoTImFh3OLFWeOKygtS47Z8fp1GYHg==" + }, + "canonicalizedBody": "eyJhcGlWZXJzaW9uIjoiMC4wLjIiLCJraW5kIjoiaW50b3RvIiwic3BlYyI6eyJjb250ZW50Ijp7ImVudmVsb3BlIjp7InBheWxvYWRUeXBlIjoidGV4dC9wbGFpbiIsInNpZ25hdHVyZXMiOlt7InB1YmxpY0tleSI6IkxTMHRMUzFDUlVkSlRpQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENrMUpTVU51ZWtORFFXbFhaMEYzU1VKQlowbFZWa2gzWldoUGRFZHVORXRUUkRGSU9GSkpOVGd4VFdaaWVXVjNkME5uV1VsTGIxcEplbW93UlVGM1RYY0tUbnBGVmsxQ1RVZEJNVlZGUTJoTlRXTXliRzVqTTFKMlkyMVZkVnBIVmpKTlVqUjNTRUZaUkZaUlVVUkZlRlo2WVZka2VtUkhPWGxhVXpGd1ltNVNiQXBqYlRGc1drZHNhR1JIVlhkSWFHTk9UV3BKZUUxVVFUUk5ha2t4VDBSQk1sZG9ZMDVOYWtsNFRWUkJORTFxVFhkUFJFRXlWMnBCUVUxR2EzZEZkMWxJQ2t0dldrbDZhakJEUVZGWlNVdHZXa2w2YWpCRVFWRmpSRkZuUVVWSFp6WklhbmgwTWxWT2FVb3hhM2QzY1RWWVVVbEpkMDFhYmtwbVZsRXpZa1l3TVhVS1drdDBaVTFrWTFZdk0zRm9RMjFYVDJWamIzaFNjWGR5WWxsVWMyaEhaemxPZVZoalFtSjJaVFo2UzNkYVZsUk1aWEZQUTBGVlVYZG5aMFpCVFVFMFJ3cEJNVlZrUkhkRlFpOTNVVVZCZDBsSVowUkJWRUpuVGxaSVUxVkZSRVJCUzBKblozSkNaMFZHUWxGalJFRjZRV1JDWjA1V1NGRTBSVVpuVVZVM1YzQlNDall3YzBOd1oyWjFNRFIzWTNOcWRrTkdlSFF3WmsxcmQwaDNXVVJXVWpCcVFrSm5kMFp2UVZVek9WQndlakZaYTBWYVlqVnhUbXB3UzBaWGFYaHBORmtLV2tRNGQwaDNXVVJXVWpCU1FWRklMMEpDVlhkRk5FVlNXVzVLY0ZsWE5VRmFSMVp2V1ZjeGJHTnBOV3BpTWpCM1RFRlpTMHQzV1VKQ1FVZEVkbnBCUWdwQlVWRmxZVWhTTUdOSVRUWk1lVGx1WVZoU2IyUlhTWFZaTWpsMFRESjRkbG95YkhWTU1qbG9aRmhTYjAxSlIwcENaMjl5UW1kRlJVRmtXalZCWjFGRENrSkljMFZsVVVJelFVaFZRVE5VTUhkaGMySklSVlJLYWtkU05HTnRWMk16UVhGS1MxaHlhbVZRU3pNdmFEUndlV2RET0hBM2J6UkJRVUZIUlZkWVkxSUtPRUZCUVVKQlRVRlNha0pGUVdsQ1VsUnlSMFUxV1RGRmJsbHVhV0ZLUWl0dWMzWTRPVlpoV1hnelVWcHFiMk5GYVc0emNqa3hkMlpyUVVsblRYTnpLd3BtYzNOMU5WTk1VV3R1TjFkRVZFdFlaMjkzTjFONFlraFpVMXBxTTNscmVFRnlWbTUxZWtWM1EyZFpTVXR2V2tsNmFqQkZRWGROUkdGQlFYZGFVVWw0Q2tGUWFsTkhaR1JNU1haNVZVMUhTV3RhSzNVMlNtaEZPWEF4VG1wME0yUkZkSGRaYTAxNFptNUZWakpyTjAxSU1VSldiWGhuT1ZCelNtcHhlV05tYVNzS1pVRkpkMFJoUzI0eVEyUlBlRXR6ZUdObldVNXBORWgyYVVWdVduRjRiV1ZFZVc4eVdVWkpkSHB3U0daSlRWRnRZMUpUYkRreFZXVlBVME00SzFCMVJ3cG5kMDFMQ2kwdExTMHRSVTVFSUVORlVsUkpSa2xEUVZSRkxTMHRMUzBLIiwic2lnIjoiVFVWVlEwbERWV2hCVm1WM1puZExiR3MxWmxaNmNGSkVWVkJvUlhjNVR6aEpNbkI0UXpWdVZHNVFabGxFUW5OUFFXbEZRVEJhUm5Gek9UbFJaMUk1YlVGMFJrMVhkRmR5VDJwdFZVTTBOM3BuWVc5dmJFdEpiMHhJTDA5M1pFMDkifV19LCJoYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiZGNiNDkyNTljODY2MDdjMzQ2MzVkYWJiNDQzMWYwNjVlOWE3YTczNDcwNGNiNzNmMGFhMGY2YWFhMzg5NmEwNCJ9LCJwYXlsb2FkSGFzaCI6eyJhbGdvcml0aG0iOiJzaGEyNTYiLCJ2YWx1ZSI6IjY4ZTY1NmIyNTFlNjdlODM1OGJlZjg0ODNhYjBkNTFjNjYxOWYzZTdhMWE5ZjBlNzU4MzhkNDFmZjM2OGY3MjgifX19fQ==" + } + ], + "timestampVerificationData": { + "rfc3161Timestamps": [] + }, + "x509CertificateChain": { + "certificates": [ + { + "rawBytes": "MIICnzCCAiWgAwIBAgIUVHwehOtGn4KSD1H8RI581MfbyewwCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjIxMTA4MjI1ODA2WhcNMjIxMTA4MjMwODA2WjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEGg6Hjxt2UNiJ1kwwq5XQIIwMZnJfVQ3bF01uZKteMdcV/3qhCmWOecoxRqwrbYTshGg9NyXcBbve6zKwZVTLeqOCAUQwggFAMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQU7WpR60sCpgfu04wcsjvCFxt0fMkwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wHwYDVR0RAQH/BBUwE4ERYnJpYW5AZGVoYW1lci5jb20wLAYKKwYBBAGDvzABAQQeaHR0cHM6Ly9naXRodWIuY29tL2xvZ2luL29hdXRoMIGJBgorBgEEAdZ5AgQCBHsEeQB3AHUA3T0wasbHETJjGR4cmWc3AqJKXrjePK3/h4pygC8p7o4AAAGEWXcR8AAABAMARjBEAiBRTrGE5Y1EnYniaJB+nsv89VaYx3QZjocEin3r91wfkAIgMss+fssu5SLQkn7WDTKXgow7SxbHYSZj3ykxArVnuzEwCgYIKoZIzj0EAwMDaAAwZQIxAPjSGddLIvyUMGIkZ+u6JhE9p1Njt3dEtwYkMxfnEV2k7MH1BVmxg9PsJjqycfi+eAIwDaKn2CdOxKsxcgYNi4HviEnZqxmeDyo2YFItzpHfIMQmcRSl91UeOSC8+PuGgwMK" + }, + { + "rawBytes": "MIICGjCCAaGgAwIBAgIUALnViVfnU0brJasmRkHrn/UnfaQwCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMjA0MTMyMDA2MTVaFw0zMTEwMDUxMzU2NThaMDcxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEeMBwGA1UEAxMVc2lnc3RvcmUtaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8RVS/ysH+NOvuDZyPIZtilgUF9NlarYpAd9HP1vBBH1U5CV77LSS7s0ZiH4nE7Hv7ptS6LvvR/STk798LVgMzLlJ4HeIfF3tHSaexLcYpSASr1kS0N/RgBJz/9jWCiXno3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU39Ppz1YkEZb5qNjpKFWixi4YZD8wHwYDVR0jBBgwFoAUWMAeX5FFpWapesyQoZMi0CrFxfowCgYIKoZIzj0EAwMDZwAwZAIwPCsQK4DYiZYDPIaDi5HFKnfxXx6ASSVmERfsynYBiX2X6SJRnZU84/9DZdnFvvxmAjBOt6QpBlc4J/0DxvkTCqpclvziL6BCCPnjdlIB3Pu3BxsPmygUY7Ii2zbdCdliiow=" + }, + { + "rawBytes": "MIIB9zCCAXygAwIBAgIUALZNAPFdxHPwjeDloDwyYChAO/4wCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMTEwMDcxMzU2NTlaFw0zMTEwMDUxMzU2NThaMCoxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjERMA8GA1UEAxMIc2lnc3RvcmUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT7XeFT4rb3PQGwS4IajtLk3/OlnpgangaBclYpsYBr5i+4ynB07ceb3LP0OIOZdxexX69c5iVuyJRQ+Hz05yi+UF3uBWAlHpiS5sh0+H2GHE7SXrk1EC5m1Tr19L9gg92jYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRYwB5fkUWlZql6zJChkyLQKsXF+jAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQKsXF+jAKBggqhkjOPQQDAwNpADBmAjEAj1nHeXZp+13NWBNa+EDsDP8G1WWg1tCMWP/WHPqpaVo0jhsweNFZgSs0eE7wYI4qAjEA2WB9ot98sIkoF3vZYdd3/VtWB5b9TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ" + } + ] + } + }, + "dsseEnvelope": { + "payload": "aGVsbG8sIHdvcmxkIQ==", + "payloadType": "text/plain", + "signatures": [ + { + "sig": "NEUCICUhAVewfwKlk5fVzpRDUPhEw9O8I2pxC5nTnPfYDBsOAiEA0ZFqs99QgR9mAtFMWtWrOjmUC47zgaoolKIoLH/OwdM=", + "keyid": "" + } + ] + } +} diff --git a/pkg/cmd/attestation/test/path.go b/pkg/cmd/attestation/test/path.go new file mode 100644 index 000000000..5b6282b7d --- /dev/null +++ b/pkg/cmd/attestation/test/path.go @@ -0,0 +1,13 @@ +package test + +import ( + "runtime" + "strings" +) + +func NormalizeRelativePath(posixPath string) string { + if runtime.GOOS == "windows" { + return strings.ReplaceAll(posixPath, "/", "\\") + } + return posixPath +} diff --git a/pkg/cmd/attestation/tufrootverify/tufrootverify.go b/pkg/cmd/attestation/tufrootverify/tufrootverify.go new file mode 100644 index 000000000..05971da54 --- /dev/null +++ b/pkg/cmd/attestation/tufrootverify/tufrootverify.go @@ -0,0 +1,83 @@ +package tufrootverify + +import ( + "fmt" + "os" + + "github.com/cli/cli/v2/pkg/cmd/attestation/auth" + "github.com/cli/cli/v2/pkg/cmd/attestation/verification" + "github.com/cli/cli/v2/pkg/cmdutil" + + "github.com/MakeNowJust/heredoc" + "github.com/sigstore/sigstore-go/pkg/tuf" + "github.com/spf13/cobra" +) + +func NewTUFRootVerifyCmd(f *cmdutil.Factory, runF func() error) *cobra.Command { + var mirror string + var root string + var cmd = cobra.Command{ + Use: "tuf-root-verify --mirror --root ", + Args: cobra.ExactArgs(0), + Short: "Verify the TUF repository from a provided TUF root", + Hidden: true, + Long: heredoc.Docf(` + Verify a TUF repository with a local TUF root. + + The command requires you provide the %[1]s--mirror%[1]s flag, which should be the URL + of the TUF repository mirror. + + The command also requires you provide the %[1]s--root%[1]s flag, which should be the + path to the TUF root file. + + GitHub relies on TUF to securely deliver the trust root for our signing authority. + For more information on TUF, see the official documentation: . + `, "`"), + Example: heredoc.Doc(` + # Verify the TUF repository from a provided TUF root + gh attestation tuf-root-verify --mirror https://tuf-repo.github.com --root /path/to/1.root.json + `), + RunE: func(cmd *cobra.Command, args []string) error { + if err := auth.IsHostSupported(); err != nil { + return err + } + + if runF != nil { + return runF() + } + + if err := tufRootVerify(mirror, root); err != nil { + return fmt.Errorf("Failed to verify the TUF repository: %w", err) + } + + io := f.IOStreams + fmt.Sprintln(io.Out, io.ColorScheme().Green("Successfully verified the TUF repository")) + return nil + }, + } + + cmd.Flags().StringVarP(&mirror, "mirror", "m", "", "URL to the TUF repository mirror") + cmd.MarkFlagRequired("mirror") //nolint:errcheck + cmd.Flags().StringVarP(&root, "root", "r", "", "Path to the TUF root file on disk") + cmd.MarkFlagRequired("root") //nolint:errcheck + + return &cmd +} + +func tufRootVerify(mirror, root string) error { + rb, err := os.ReadFile(root) + if err != nil { + return fmt.Errorf("failed to read root file %s: %v", root, err) + } + opts := verification.GitHubTUFOptions() + opts.Root = rb + opts.RepositoryBaseURL = mirror + // The purpose is the verify the TUF root and repository, make + // sure there is no caching enabled + opts.CacheValidity = 0 + if _, err = tuf.New(opts); err != nil { + return fmt.Errorf("failed to create TUF client: %v", err) + } + + return nil +} diff --git a/pkg/cmd/attestation/tufrootverify/tufrootverify_test.go b/pkg/cmd/attestation/tufrootverify/tufrootverify_test.go new file mode 100644 index 000000000..004f25618 --- /dev/null +++ b/pkg/cmd/attestation/tufrootverify/tufrootverify_test.go @@ -0,0 +1,80 @@ +package tufrootverify + +import ( + "bytes" + "strings" + "testing" + + "github.com/cli/cli/v2/pkg/cmd/attestation/test" + "github.com/cli/cli/v2/pkg/cmdutil" + "github.com/cli/cli/v2/pkg/iostreams" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewTUFRootVerifyCmd(t *testing.T) { + testIO, _, _, _ := iostreams.Test() + f := &cmdutil.Factory{ + IOStreams: testIO, + } + + testcases := []struct { + name string + cli string + wantsErr bool + }{ + { + name: "Missing mirror flag", + cli: "--root ../verification/embed/tuf-repo.github.com/root.json", + wantsErr: true, + }, + { + name: "Missing root flag", + cli: "--mirror https://tuf-repo.github.com", + wantsErr: true, + }, + { + name: "Has all required flags", + cli: "--mirror https://tuf-repo.github.com --root ../verification/embed/tuf-repo.github.com/root.json", + wantsErr: false, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + cmd := NewTUFRootVerifyCmd(f, func() error { + return nil + }) + + argv := strings.Split(tc.cli, " ") + cmd.SetArgs(argv) + cmd.SetIn(&bytes.Buffer{}) + cmd.SetOut(&bytes.Buffer{}) + cmd.SetErr(&bytes.Buffer{}) + _, err := cmd.ExecuteC() + if tc.wantsErr { + assert.Error(t, err) + return + } + assert.NoError(t, err) + }) + } +} + +func TestTUFRootVerify(t *testing.T) { + mirror := "https://tuf-repo.github.com" + root := test.NormalizeRelativePath("../verification/embed/tuf-repo.github.com/root.json") + + t.Run("successfully verifies TUF root", func(t *testing.T) { + err := tufRootVerify(mirror, root) + require.NoError(t, err) + }) + + t.Run("fails because the root cannot be found", func(t *testing.T) { + notFoundRoot := test.NormalizeRelativePath("./does/not/exist/root.json") + err := tufRootVerify(mirror, notFoundRoot) + require.Error(t, err) + require.ErrorContains(t, err, "failed to read root file") + }) +} diff --git a/pkg/cmd/attestation/verification/attestation.go b/pkg/cmd/attestation/verification/attestation.go new file mode 100644 index 000000000..7fb4a1615 --- /dev/null +++ b/pkg/cmd/attestation/verification/attestation.go @@ -0,0 +1,115 @@ +package verification + +import ( + "bufio" + "errors" + "fmt" + "os" + "path/filepath" + + "github.com/cli/cli/v2/pkg/cmd/attestation/api" + protobundle "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1" + "github.com/sigstore/sigstore-go/pkg/bundle" +) + +var ErrLocalAttestations = errors.New("failed to load local attestations") + +type FetchAttestationsConfig struct { + APIClient api.Client + BundlePath string + Digest string + Limit int + Owner string + Repo string +} + +func (c *FetchAttestationsConfig) IsBundleProvided() bool { + return c.BundlePath != "" +} + +func GetAttestations(c FetchAttestationsConfig) ([]*api.Attestation, error) { + if c.IsBundleProvided() { + return GetLocalAttestations(c.BundlePath) + } + return GetRemoteAttestations(c) +} + +// GetLocalAttestations returns a slice of attestations read from a local bundle file. +func GetLocalAttestations(path string) ([]*api.Attestation, error) { + fileExt := filepath.Ext(path) + switch fileExt { + case ".json": + attestations, err := loadBundleFromJSONFile(path) + if err != nil { + return nil, fmt.Errorf("bundle could not be loaded from JSON file: %v", err) + } + return attestations, nil + case ".jsonl": + attestations, err := loadBundlesFromJSONLinesFile(path) + if err != nil { + return nil, fmt.Errorf("bundles could not be loaded from JSON lines file: %v", err) + } + return attestations, nil + } + return nil, ErrLocalAttestations +} + +func loadBundleFromJSONFile(path string) ([]*api.Attestation, error) { + localAttestation, err := bundle.LoadJSONFromPath(path) + if err != nil { + return nil, err + } + + return []*api.Attestation{{Bundle: localAttestation}}, nil +} + +func loadBundlesFromJSONLinesFile(path string) ([]*api.Attestation, error) { + file, err := os.Open(path) + if err != nil { + return nil, fmt.Errorf("could not open file: %v", err) + } + defer file.Close() + + attestations := []*api.Attestation{} + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + b := scanner.Bytes() + var bundle bundle.ProtobufBundle + bundle.Bundle = new(protobundle.Bundle) + err = bundle.UnmarshalJSON(b) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal bundle from JSON: %v", err) + } + a := api.Attestation{Bundle: &bundle} + attestations = append(attestations, &a) + } + + if err := scanner.Err(); err != nil { + return nil, err + } + + return attestations, nil +} + +func GetRemoteAttestations(c FetchAttestationsConfig) ([]*api.Attestation, error) { + if c.APIClient == nil { + return nil, fmt.Errorf("api client must be provided") + } + // check if Repo is set first because if Repo has been set, Owner will be set using the value of Repo. + // If Repo is not set, the field will remain empty. It will not be populated using the value of Owner. + if c.Repo != "" { + attestations, err := c.APIClient.GetByRepoAndDigest(c.Repo, c.Digest, c.Limit) + if err != nil { + return nil, fmt.Errorf("failed to fetch attestations from %s: %w", c.Repo, err) + } + return attestations, nil + } else if c.Owner != "" { + attestations, err := c.APIClient.GetByOwnerAndDigest(c.Owner, c.Digest, c.Limit) + if err != nil { + return nil, fmt.Errorf("failed to fetch attestations from %s: %w", c.Owner, err) + } + return attestations, nil + } + return nil, fmt.Errorf("owner or repo must be provided") +} diff --git a/pkg/cmd/attestation/verification/attestation_test.go b/pkg/cmd/attestation/verification/attestation_test.go new file mode 100644 index 000000000..b178a0682 --- /dev/null +++ b/pkg/cmd/attestation/verification/attestation_test.go @@ -0,0 +1,49 @@ +package verification + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestLoadBundlesFromJSONLinesFile(t *testing.T) { + path := "../test/data/sigstore-js-2.1.0_with_2_bundles.jsonl" + attestations, err := loadBundlesFromJSONLinesFile(path) + + require.NoError(t, err) + require.Len(t, attestations, 2) +} + +func TestLoadBundleFromJSONFile(t *testing.T) { + path := "../test/data/sigstore-js-2.1.0-bundle.json" + attestations, err := loadBundleFromJSONFile(path) + + require.NoError(t, err) + require.Len(t, attestations, 1) +} + +func TestGetLocalAttestations(t *testing.T) { + t.Run("with JSON file containing one bundle", func(t *testing.T) { + path := "../test/data/sigstore-js-2.1.0-bundle.json" + attestations, err := GetLocalAttestations(path) + + require.NoError(t, err) + require.Len(t, attestations, 1) + }) + + t.Run("with JSON lines file containing multiple bundles", func(t *testing.T) { + path := "../test/data/sigstore-js-2.1.0_with_2_bundles.jsonl" + attestations, err := GetLocalAttestations(path) + + require.NoError(t, err) + require.Len(t, attestations, 2) + }) + + t.Run("with file with unrecognized extension", func(t *testing.T) { + path := "../test/data/sigstore-js-2.1.0-bundles.tgz" + attestations, err := GetLocalAttestations(path) + + require.ErrorIs(t, err, ErrLocalAttestations) + require.Nil(t, attestations) + }) +} diff --git a/pkg/cmd/attestation/verification/embed/tuf-repo.github.com/root.json b/pkg/cmd/attestation/verification/embed/tuf-repo.github.com/root.json new file mode 100644 index 000000000..2990a2665 --- /dev/null +++ b/pkg/cmd/attestation/verification/embed/tuf-repo.github.com/root.json @@ -0,0 +1,163 @@ +{ + "signatures": [ + { + "keyid": "a10513a5ab61acd0c6b6fbe0504856ead18f3b17c4fabbe3fa848c79a5a187cf", + "sig": "304402203c8f5f7443f7052923e82f9ca0b1bb61a33498444076a2f43e1285a47f6e562d022014de99a7e5413440896b6804944e3c49390cfe6e617211b8dc42a8e67675bc13" + }, + { + "keyid": "d6a89e23fb22801a0d1186bf1bdd007e228f65a8aa9964d24d06cb5fbb0ce91c", + "sig": "3044022009a20307f974af7e05cc9564eea497f45062e3b21272d1062713b3d22c868298022059d032ad973a28bdbd03959cf96b21398b6b6e2ca618c17ce6c13712246343a2" + }, + { + "keyid": "539dde44014c850fe6eeb8b299eb7dae2e1f4bf83454b949e98aa73542cdc65a", + "sig": "3045022100edd270d36d0c8468b9a1f2ef1c81a270c72ffd50c65bca0ed1ebd424df09f64b022002b27ffafd7bc5bdfc25281b5b9b597cf2d67d4eeb4af2ff45eb3e666b860c21" + }, + { + "keyid": "88737ccdac7b49cc237e9aaead81be2a40278b886a693d8149a19cf543f093d3", + "sig": "30460221008d7d95434e576d5876b2db30fd645505ca546618bbc7a8e4b39f64e6a36df9ad022100c00a5294e4ddd02d48d28918b87a06bdfdeccd0618ecdcec29bb2597a05fe474" + }, + { + "keyid": "5e01c9a0b2641a8965a4a74e7df0bc7b2d8278a2c3ca0cf7a3f2f783d3c69800", + "sig": "30450220215fb3d19d94560a3a2a6067a71c92daf867d13700c9500c4c32d8009a48a634022100df9fb6cee786313bf6c363daac4de39b3dd531f211f81d2391c41bd2d0f91a80" + }, + { + "keyid": "4f4d1dd75f2d7f3860e3a068d7bed90dec5f0faafcbe1ace7fb7d95d29e07228", + "sig": "304502204091ac5e61b6462d262ecc8442781dd09843bed39942a95a4884c8c6a2c212ef022100dcee86392748f48950d04d539ac1a6643ed1f0b4bd6856f8aeb5a135826c846f" + }, + { + "keyid": "eb8eff37f93af2faaba519f341decec3cecd3eeafcace32966db9723842c8a62", + "sig": "30460221009188548601a43b501223caeefca4876ae892e18a85c885004b4c8aeeb05a4421022100abdcc72d94597f8297d6297897ff96f285168dbe6b3d57f846dbc7a2948d2935" + }, + { + "keyid": "8b498a80a1b7af188c10c9abdf6aade81d14faaffcde2abcd6063baa673ebd12", + "sig": "3046022100b440561545d48759dc4140cda9f8af7c9405a101d6136dd0a26edd6562b7064f022100cafa917ed90350494e47d226b64a8ec63ef5ceebb8ba4d2dec2ce018e4ad402a" + } + ], + "signed": { + "_type": "root", + "consistent_snapshot": true, + "expires": "2024-06-23T08:29:18Z", + "keys": { + "4f4d1dd75f2d7f3860e3a068d7bed90dec5f0faafcbe1ace7fb7d95d29e07228": { + "keytype": "ecdsa", + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENki7aZVips5SgRzCd/Om0CGzQKY/\nnv84giqVDmdwb2ys82Z6soFLasvYYEEQcwqaC170n9gr93wHUgPc796uJA==\n-----END PUBLIC KEY-----\n" + }, + "scheme": "ecdsa-sha2-nistp256", + "x-tuf-on-ci-keyowner": "@ashtom" + }, + "539dde44014c850fe6eeb8b299eb7dae2e1f4bf83454b949e98aa73542cdc65a": { + "keytype": "ecdsa", + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAElD0o2sOZN9n3RKQ7PtMLAoXj+2Ai\nn4PKT/pfnzDlNLrD3VTQwCc4sR4t+OLu4KQ+qk+kXkR9YuBsu3bdJZ1OWw==\n-----END PUBLIC KEY-----\n" + }, + "scheme": "ecdsa-sha2-nistp256", + "x-tuf-on-ci-keyowner": "@nerdneha" + }, + "5e01c9a0b2641a8965a4a74e7df0bc7b2d8278a2c3ca0cf7a3f2f783d3c69800": { + "keytype": "ecdsa", + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC9RNAsuDCNO6T7qA7Y5F8orw2tIW\nr7rUr4ffxvzTMrbkVtjR/trtE0q0+T0zQ8TWLyI6EYMwb947ej2ItfkOyA==\n-----END PUBLIC KEY-----\n" + }, + "scheme": "ecdsa-sha2-nistp256", + "x-tuf-on-ci-keyowner": "@jacobdepriest" + }, + "88737ccdac7b49cc237e9aaead81be2a40278b886a693d8149a19cf543f093d3": { + "keytype": "ecdsa", + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEBagkskNOpOTbetTX5CdnvMy+LiWn\nonRrNrqAHL4WgiebH7Uig7GLhC3bkeA/qgb926/vr9qhOPG9Buj2HatrPw==\n-----END PUBLIC KEY-----\n" + }, + "scheme": "ecdsa-sha2-nistp256", + "x-tuf-on-ci-keyowner": "@gregose" + }, + "8b498a80a1b7af188c10c9abdf6aade81d14faaffcde2abcd6063baa673ebd12": { + "keytype": "ecdsa", + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE7IEoVNwrprchXGhT5sAhSax7SOd3\n8duuISghCzfmHdKJWSbV2wJRamRiUVRtmA83K/qm5cT20WXMCT5QeM/D3A==\n-----END PUBLIC KEY-----\n" + }, + "scheme": "ecdsa-sha2-nistp256", + "x-tuf-on-ci-keyowner": "@trevrosen" + }, + "a10513a5ab61acd0c6b6fbe0504856ead18f3b17c4fabbe3fa848c79a5a187cf": { + "keytype": "ecdsa", + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC2wJ3xscyXxBLybJ9FVjwkyQMe53\nRHUz77AjMO8MzVaT8xw6ZvJqdNZiytYtigWULlINxw6frNsWJKb/f7lC8A==\n-----END PUBLIC KEY-----\n" + }, + "scheme": "ecdsa-sha2-nistp256", + "x-tuf-on-ci-keyowner": "@kommendorkapten" + }, + "d6a89e23fb22801a0d1186bf1bdd007e228f65a8aa9964d24d06cb5fbb0ce91c": { + "keytype": "ecdsa", + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDdORwcruW3gqAgaLjH/nNdGMB4kQ\nAvA+wD6DyO4P/wR8ee2ce83NZHq1ZADKhve0rlYKaKy3CqyQ5SmlZ36Zhw==\n-----END PUBLIC KEY-----\n" + }, + "scheme": "ecdsa-sha2-nistp256", + "x-tuf-on-ci-keyowner": "@krukow" + }, + "eb8eff37f93af2faaba519f341decec3cecd3eeafcace32966db9723842c8a62": { + "keytype": "ecdsa", + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENynVdQnM9h7xU71G7PiJpQaDemub\nkbjsjYwLlPJTQVuxQO8WeIpJf8MEh5rf01t2dDIuCsZ5gRx+QvDv0UzfsA==\n-----END PUBLIC KEY-----\n" + }, + "scheme": "ecdsa-sha2-nistp256", + "x-tuf-on-ci-keyowner": "@mph4" + }, + "eb9799b483affac9da87ef4c9ea467928415c961349e607e5e6e485679b07f8f": { + "keytype": "ecdsa", + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENKNcNcX+d73lS1TRFb9Vnp8JvOoh\nzYQ+in43iGenbG8RGo9L/6FJ2hoRbVU6xskvyuErcdPbCdI4GxrQ5i8hkw==\n-----END PUBLIC KEY-----\n" + }, + "scheme": "ecdsa-sha2-nistp256", + "x-tuf-on-ci-online-uri": "azurekms://production-tuf-root.vault.azure.net/keys/Online-Key/aaf375fd8ed24acb949a5cc173700b05" + } + }, + "roles": { + "root": { + "keyids": [ + "a10513a5ab61acd0c6b6fbe0504856ead18f3b17c4fabbe3fa848c79a5a187cf", + "4f4d1dd75f2d7f3860e3a068d7bed90dec5f0faafcbe1ace7fb7d95d29e07228", + "88737ccdac7b49cc237e9aaead81be2a40278b886a693d8149a19cf543f093d3", + "5e01c9a0b2641a8965a4a74e7df0bc7b2d8278a2c3ca0cf7a3f2f783d3c69800", + "d6a89e23fb22801a0d1186bf1bdd007e228f65a8aa9964d24d06cb5fbb0ce91c", + "eb8eff37f93af2faaba519f341decec3cecd3eeafcace32966db9723842c8a62", + "8b498a80a1b7af188c10c9abdf6aade81d14faaffcde2abcd6063baa673ebd12", + "539dde44014c850fe6eeb8b299eb7dae2e1f4bf83454b949e98aa73542cdc65a" + ], + "threshold": 3 + }, + "snapshot": { + "keyids": [ + "eb9799b483affac9da87ef4c9ea467928415c961349e607e5e6e485679b07f8f" + ], + "threshold": 1, + "x-tuf-on-ci-expiry-period": 21, + "x-tuf-on-ci-signing-period": 7 + }, + "targets": { + "keyids": [ + "a10513a5ab61acd0c6b6fbe0504856ead18f3b17c4fabbe3fa848c79a5a187cf", + "4f4d1dd75f2d7f3860e3a068d7bed90dec5f0faafcbe1ace7fb7d95d29e07228", + "88737ccdac7b49cc237e9aaead81be2a40278b886a693d8149a19cf543f093d3", + "5e01c9a0b2641a8965a4a74e7df0bc7b2d8278a2c3ca0cf7a3f2f783d3c69800", + "d6a89e23fb22801a0d1186bf1bdd007e228f65a8aa9964d24d06cb5fbb0ce91c", + "eb8eff37f93af2faaba519f341decec3cecd3eeafcace32966db9723842c8a62", + "8b498a80a1b7af188c10c9abdf6aade81d14faaffcde2abcd6063baa673ebd12", + "539dde44014c850fe6eeb8b299eb7dae2e1f4bf83454b949e98aa73542cdc65a" + ], + "threshold": 3 + }, + "timestamp": { + "keyids": [ + "eb9799b483affac9da87ef4c9ea467928415c961349e607e5e6e485679b07f8f" + ], + "threshold": 1, + "x-tuf-on-ci-expiry-period": 7, + "x-tuf-on-ci-signing-period": 6 + } + }, + "spec_version": "1.0.31", + "version": 1, + "x-tuf-on-ci-expiry-period": 240, + "x-tuf-on-ci-signing-period": 60 + } +} diff --git a/pkg/cmd/attestation/verification/policy.go b/pkg/cmd/attestation/verification/policy.go new file mode 100644 index 000000000..91b54c75e --- /dev/null +++ b/pkg/cmd/attestation/verification/policy.go @@ -0,0 +1,20 @@ +package verification + +import ( + "encoding/hex" + + "github.com/cli/cli/v2/pkg/cmd/attestation/artifact" + + "github.com/sigstore/sigstore-go/pkg/verify" +) + +// BuildDigestPolicyOption builds a verify.ArtifactPolicyOption +// from the given artifact digest and digest algorithm +func BuildDigestPolicyOption(a artifact.DigestedArtifact) (verify.ArtifactPolicyOption, error) { + // sigstore-go expects the artifact digest to be decoded from hex + decoded, err := hex.DecodeString(a.Digest()) + if err != nil { + return nil, err + } + return verify.WithArtifactDigest(a.Algorithm(), decoded), nil +} diff --git a/pkg/cmd/attestation/verification/sigstore.go b/pkg/cmd/attestation/verification/sigstore.go new file mode 100644 index 000000000..daaacf628 --- /dev/null +++ b/pkg/cmd/attestation/verification/sigstore.go @@ -0,0 +1,208 @@ +package verification + +import ( + "fmt" + + "github.com/cli/cli/v2/pkg/cmd/attestation/api" + "github.com/cli/cli/v2/pkg/cmd/attestation/io" + + "github.com/sigstore/sigstore-go/pkg/bundle" + "github.com/sigstore/sigstore-go/pkg/root" + "github.com/sigstore/sigstore-go/pkg/tuf" + "github.com/sigstore/sigstore-go/pkg/verify" +) + +const ( + PublicGoodIssuerOrg = "sigstore.dev" + GitHubIssuerOrg = "GitHub, Inc." +) + +// AttestationProcessingResult captures processing a given attestation's signature verification and policy evaluation +type AttestationProcessingResult struct { + Attestation *api.Attestation `json:"attestation"` + VerificationResult *verify.VerificationResult `json:"verificationResult"` +} + +type SigstoreResults struct { + VerifyResults []*AttestationProcessingResult + Error error +} + +type SigstoreConfig struct { + CustomTrustedRoot string + Logger *io.Handler + NoPublicGood bool +} + +type SigstoreVerifier struct { + ghVerifier *verify.SignedEntityVerifier + publicGoodVerifier *verify.SignedEntityVerifier + customVerifier *verify.SignedEntityVerifier + policy verify.PolicyBuilder + onlyVerifyWithGithub bool + Logger *io.Handler +} + +// NewSigstoreVerifier creates a new SigstoreVerifier struct +// that is used to verify artifacts and attestations against the +// Public Good, GitHub, or a custom trusted root. +func NewSigstoreVerifier(config SigstoreConfig, policy verify.PolicyBuilder) (*SigstoreVerifier, error) { + customVerifier, err := newCustomVerifier(config.CustomTrustedRoot) + if err != nil { + return nil, fmt.Errorf("failed to create custom verifier: %v", err) + } + + publicGoodVerifier, err := newPublicGoodVerifier() + if err != nil { + return nil, fmt.Errorf("failed to create Public Good Sigstore verifier: %v", err) + } + + ghVerifier, err := newGitHubVerifier() + if err != nil { + return nil, fmt.Errorf("failed to create GitHub Sigstore verifier: %v", err) + } + + return &SigstoreVerifier{ + ghVerifier: ghVerifier, + publicGoodVerifier: publicGoodVerifier, + customVerifier: customVerifier, + Logger: config.Logger, + policy: policy, + onlyVerifyWithGithub: config.NoPublicGood, + }, nil +} + +func (v *SigstoreVerifier) chooseVerifier(b *bundle.ProtobufBundle) (*verify.SignedEntityVerifier, string, error) { + verifyContent, err := b.VerificationContent() + if err != nil { + return nil, "", fmt.Errorf("failed to get bundle verification content: %v", err) + } + leafCert, ok := verifyContent.HasCertificate() + if !ok { + return nil, "", fmt.Errorf("leaf cert not found") + } + if len(leafCert.Issuer.Organization) != 1 { + return nil, "", fmt.Errorf("expected the leaf certificate issuer to only have one organization") + } + issuer := leafCert.Issuer.Organization[0] + + // if user provided a custom trusted root file path, use the custom verifier + if v.customVerifier != nil { + return v.customVerifier, issuer, nil + } + + if v.onlyVerifyWithGithub { + return v.ghVerifier, issuer, nil + } + + if leafCert.Issuer.Organization[0] == PublicGoodIssuerOrg { + return v.publicGoodVerifier, issuer, nil + } else if leafCert.Issuer.Organization[0] == GitHubIssuerOrg { + return v.ghVerifier, issuer, nil + } + return nil, "", fmt.Errorf("leaf certificate issuer is not recognized") +} + +func (v *SigstoreVerifier) Verify(attestations []*api.Attestation) *SigstoreResults { + // initialize the processing results before attempting to verify + // with multiple verifiers + results := make([]*AttestationProcessingResult, len(attestations)) + for i, att := range attestations { + apr := &AttestationProcessingResult{ + Attestation: att, + } + results[i] = apr + } + + totalAttestations := len(attestations) + for i, apr := range results { + v.Logger.VerbosePrintf("Verifying attestation %d/%d against the configured Sigstore trust roots\n", i+1, totalAttestations) + + // determine which verifier should attempt verification against the bundle + verifier, issuer, err := v.chooseVerifier(apr.Attestation.Bundle) + if err != nil { + return &SigstoreResults{ + Error: fmt.Errorf("failed to find recognized issuer from bundle content: %v", err), + } + } + + v.Logger.VerbosePrintf("Attempting verification against issuer \"%s\"\n", issuer) + // attempt to verify the attestation + result, err := verifier.Verify(apr.Attestation.Bundle, v.policy) + // if verification fails, create the error and exit verification early + if err != nil { + v.Logger.VerbosePrint(v.Logger.ColorScheme.Redf( + "Failed to verify against issuer \"%s\" \n\n", issuer, + )) + + return &SigstoreResults{ + Error: fmt.Errorf("verifying with issuer \"%s\": %v", issuer, err), + } + } + + // if verification is successful, add the result + // to the AttestationProcessingResult entry + v.Logger.VerbosePrint(v.Logger.ColorScheme.Greenf( + "SUCCESS - attestation signature verified with \"%s\"\n", issuer, + )) + apr.VerificationResult = result + } + + return &SigstoreResults{ + VerifyResults: results, + } +} + +func newCustomVerifier(trustedRootFilePath string) (*verify.SignedEntityVerifier, error) { + if trustedRootFilePath == "" { + return nil, nil + } + + trustedRoot, err := root.NewTrustedRootFromPath(trustedRootFilePath) + if err != nil { + return nil, fmt.Errorf("failed to create trusted root from file %s: %v", trustedRootFilePath, err) + } + + gv, err := verify.NewSignedEntityVerifier(trustedRoot, verify.WithSignedTimestamps(1)) + if err != nil { + return nil, fmt.Errorf("failed to create custom verifier: %v", err) + } + + return gv, nil +} + +func newGitHubVerifier() (*verify.SignedEntityVerifier, error) { + opts := GitHubTUFOptions() + client, err := tuf.New(opts) + if err != nil { + return nil, fmt.Errorf("failed to create TUF client: %v", err) + } + trustedRoot, err := root.GetTrustedRoot(client) + if err != nil { + return nil, err + } + gv, err := verify.NewSignedEntityVerifier(trustedRoot, verify.WithSignedTimestamps(1)) + if err != nil { + return nil, fmt.Errorf("failed to create GitHub verifier: %v", err) + } + + return gv, nil +} + +func newPublicGoodVerifier() (*verify.SignedEntityVerifier, error) { + client, err := tuf.DefaultClient() + if err != nil { + return nil, fmt.Errorf("failed to create TUF client: %v", err) + } + trustedRoot, err := root.GetTrustedRoot(client) + if err != nil { + return nil, fmt.Errorf("failed to get trusted root: %v", err) + } + + sv, err := verify.NewSignedEntityVerifier(trustedRoot, verify.WithSignedCertificateTimestamps(1), verify.WithTransparencyLog(1), verify.WithObserverTimestamps(1)) + if err != nil { + return nil, fmt.Errorf("failed to create Public Good verifier: %v", err) + } + + return sv, nil +} diff --git a/pkg/cmd/attestation/verification/sigstore_test.go b/pkg/cmd/attestation/verification/sigstore_test.go new file mode 100644 index 000000000..204b5e583 --- /dev/null +++ b/pkg/cmd/attestation/verification/sigstore_test.go @@ -0,0 +1,60 @@ +package verification + +import ( + "testing" + + "github.com/cli/cli/v2/pkg/cmd/attestation/artifact" + "github.com/cli/cli/v2/pkg/cmd/attestation/io" + "github.com/cli/cli/v2/pkg/cmd/attestation/test" + + "github.com/sigstore/sigstore-go/pkg/verify" + "github.com/stretchr/testify/require" +) + +func buildPolicy(a artifact.DigestedArtifact) (verify.PolicyBuilder, error) { + artifactDigestPolicyOption, err := BuildDigestPolicyOption(a) + if err != nil { + return verify.PolicyBuilder{}, err + } + + policy := verify.NewPolicy(artifactDigestPolicyOption, verify.WithoutIdentitiesUnsafe()) + return policy, nil +} + +func TestNewSigstoreVerifier(t *testing.T) { + artifactPath := test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0.tgz") + artifact, err := artifact.NewDigestedArtifact(nil, artifactPath, "sha512") + require.NoError(t, err) + + policy, err := buildPolicy(*artifact) + require.NoError(t, err) + + c := SigstoreConfig{ + Logger: io.NewTestHandler(), + } + verifier, err := NewSigstoreVerifier(c, policy) + require.NoError(t, err) + + t.Run("with invalid signature", func(t *testing.T) { + bundlePath := test.NormalizeRelativePath("../test/data/sigstoreBundle-invalid-signature.json") + attestations, err := GetLocalAttestations(bundlePath) + require.NotNil(t, attestations) + require.NoError(t, err) + + res := verifier.Verify(attestations) + require.Error(t, res.Error) + require.ErrorContains(t, res.Error, "verifying with issuer \"sigstore.dev\"") + require.Nil(t, res.VerifyResults) + }) + + t.Run("with valid artifact and JSON lines file containing multiple Sigstore bundles", func(t *testing.T) { + bundlePath := test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0_with_2_bundles.jsonl") + attestations, err := GetLocalAttestations(bundlePath) + require.Len(t, attestations, 2) + require.NoError(t, err) + + res := verifier.Verify(attestations) + require.Len(t, res.VerifyResults, 2) + require.NoError(t, res.Error) + }) +} diff --git a/pkg/cmd/attestation/verification/tuf.go b/pkg/cmd/attestation/verification/tuf.go new file mode 100644 index 000000000..b87f466e8 --- /dev/null +++ b/pkg/cmd/attestation/verification/tuf.go @@ -0,0 +1,41 @@ +package verification + +import ( + _ "embed" + "os" + "path/filepath" + + "github.com/cli/go-gh/v2/pkg/config" + "github.com/sigstore/sigstore-go/pkg/tuf" +) + +//go:embed embed/tuf-repo.github.com/root.json +var githubRoot []byte + +const GitHubTUFMirror = "https://tuf-repo.github.com" + +func DefaultOptionsWithCacheSetting() *tuf.Options { + opts := tuf.DefaultOptions() + + // The CODESPACES environment variable will be set to true in a Codespaces workspace + if os.Getenv("CODESPACES") == "true" { + // if the tool is being used in a Codespace, disable the local cache + // because there is a permissions issue preventing the tuf library + // from writing the Sigstore cache to the home directory + opts.DisableLocalCache = true + } + + // Set the cache path to a directory owned by the CLI + opts.CachePath = filepath.Join(config.CacheDir(), ".sigstore", "root") + + return opts +} + +func GitHubTUFOptions() *tuf.Options { + opts := DefaultOptionsWithCacheSetting() + + opts.Root = githubRoot + opts.RepositoryBaseURL = GitHubTUFMirror + + return opts +} diff --git a/pkg/cmd/attestation/verification/tuf_test.go b/pkg/cmd/attestation/verification/tuf_test.go new file mode 100644 index 000000000..7d816bf82 --- /dev/null +++ b/pkg/cmd/attestation/verification/tuf_test.go @@ -0,0 +1,20 @@ +package verification + +import ( + "os" + "path/filepath" + "testing" + + "github.com/cli/go-gh/v2/pkg/config" + "github.com/stretchr/testify/require" +) + +func TestGitHubTUFOptions(t *testing.T) { + os.Setenv("CODESPACES", "true") + opts := GitHubTUFOptions() + + require.Equal(t, GitHubTUFMirror, opts.RepositoryBaseURL) + require.NotNil(t, opts.Root) + require.True(t, opts.DisableLocalCache) + require.Equal(t, filepath.Join(config.CacheDir(), ".sigstore", "root"), opts.CachePath) +} diff --git a/pkg/cmd/attestation/verify/options.go b/pkg/cmd/attestation/verify/options.go new file mode 100644 index 000000000..d7742bf3a --- /dev/null +++ b/pkg/cmd/attestation/verify/options.go @@ -0,0 +1,83 @@ +package verify + +import ( + "fmt" + "path/filepath" + "strings" + + "github.com/cli/cli/v2/pkg/cmd/attestation/api" + "github.com/cli/cli/v2/pkg/cmd/attestation/artifact/oci" + "github.com/cli/cli/v2/pkg/cmd/attestation/io" + "github.com/cli/cli/v2/pkg/cmdutil" +) + +// Options captures the options for the verify command +type Options struct { + ArtifactPath string + BundlePath string + CustomTrustedRoot string + DenySelfHostedRunner bool + DigestAlgorithm string + NoPublicGood bool + OIDCIssuer string + Owner string + Repo string + SAN string + SANRegex string + APIClient api.Client + Logger *io.Handler + Limit int + OCIClient oci.Client + exporter cmdutil.Exporter +} + +// Clean cleans the file path option values +func (opts *Options) Clean() { + if opts.BundlePath != "" { + opts.BundlePath = filepath.Clean(opts.BundlePath) + } +} + +func (opts *Options) SetPolicyFlags() { + // check that Repo is in the expected format if provided + if opts.Repo != "" { + // we expect the repo argument to be in the format / + splitRepo := strings.Split(opts.Repo, "/") + + // if Repo is provided but owner is not, set the OWNER portion of the Repo value + // to Owner + opts.Owner = splitRepo[0] + + if opts.SAN == "" && opts.SANRegex == "" { + opts.SANRegex = expandToGitHubURL(opts.Repo) + } + return + } + if opts.SAN == "" && opts.SANRegex == "" { + opts.SANRegex = expandToGitHubURL(opts.Owner) + } +} + +// AreFlagsValid checks that the provided flag combination is valid +// and returns an error otherwise +func (opts *Options) AreFlagsValid() error { + // check that Repo is in the expected format if provided + if opts.Repo != "" { + // we expect the repo argument to be in the format / + splitRepo := strings.Split(opts.Repo, "/") + if len(splitRepo) != 2 { + return fmt.Errorf("invalid value provided for repo: %s", opts.Repo) + } + } + + // Check that limit is between 1 and 1000 + if opts.Limit < 1 || opts.Limit > 1000 { + return fmt.Errorf("limit %d not allowed, must be between 1 and 1000", opts.Limit) + } + + return nil +} + +func expandToGitHubURL(ownerOrRepo string) string { + return fmt.Sprintf("^https://github.com/%s/", ownerOrRepo) +} diff --git a/pkg/cmd/attestation/verify/options_test.go b/pkg/cmd/attestation/verify/options_test.go new file mode 100644 index 000000000..a7430017e --- /dev/null +++ b/pkg/cmd/attestation/verify/options_test.go @@ -0,0 +1,119 @@ +package verify + +import ( + "testing" + + "github.com/cli/cli/v2/pkg/cmd/attestation/test" + + "github.com/stretchr/testify/require" +) + +var ( + publicGoodArtifactPath = test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0.tgz") + publicGoodBundlePath = test.NormalizeRelativePath("../test/data/psigstore-js-2.1.0-bundle.json") +) + +func TestAreFlagsValid(t *testing.T) { + t.Run("has invalid Repo value", func(t *testing.T) { + opts := Options{ + ArtifactPath: publicGoodArtifactPath, + DigestAlgorithm: "sha512", + OIDCIssuer: "some issuer", + Repo: "sigstoresigstore-js", + } + + err := opts.AreFlagsValid() + require.Error(t, err) + require.ErrorContains(t, err, "invalid value provided for repo") + }) + + t.Run("invalid limit < 0", func(t *testing.T) { + opts := Options{ + ArtifactPath: publicGoodArtifactPath, + BundlePath: publicGoodBundlePath, + DigestAlgorithm: "sha512", + Owner: "sigstore", + OIDCIssuer: "some issuer", + Limit: 0, + } + + err := opts.AreFlagsValid() + require.Error(t, err) + require.ErrorContains(t, err, "limit 0 not allowed, must be between 1 and 1000") + }) + + t.Run("invalid limit > 1000", func(t *testing.T) { + opts := Options{ + ArtifactPath: publicGoodArtifactPath, + BundlePath: publicGoodBundlePath, + DigestAlgorithm: "sha512", + Owner: "sigstore", + OIDCIssuer: "some issuer", + Limit: 1001, + } + + err := opts.AreFlagsValid() + require.Error(t, err) + require.ErrorContains(t, err, "limit 1001 not allowed, must be between 1 and 1000") + }) +} + +func TestSetPolicyFlags(t *testing.T) { + t.Run("sets Owner and SANRegex when Repo is provided", func(t *testing.T) { + opts := Options{ + ArtifactPath: publicGoodArtifactPath, + DigestAlgorithm: "sha512", + OIDCIssuer: "some issuer", + Repo: "sigstore/sigstore-js", + } + + opts.SetPolicyFlags() + require.Equal(t, "sigstore", opts.Owner) + require.Equal(t, "sigstore/sigstore-js", opts.Repo) + require.Equal(t, "^https://github.com/sigstore/sigstore-js/", opts.SANRegex) + }) + + t.Run("does not set SANRegex when SANRegex and Repo are provided", func(t *testing.T) { + opts := Options{ + ArtifactPath: publicGoodArtifactPath, + DigestAlgorithm: "sha512", + OIDCIssuer: "some issuer", + Repo: "sigstore/sigstore-js", + SANRegex: "^https://github/foo", + } + + opts.SetPolicyFlags() + require.Equal(t, "sigstore", opts.Owner) + require.Equal(t, "sigstore/sigstore-js", opts.Repo) + require.Equal(t, "^https://github/foo", opts.SANRegex) + }) + + t.Run("sets SANRegex when Owner is provided", func(t *testing.T) { + opts := Options{ + ArtifactPath: publicGoodArtifactPath, + BundlePath: publicGoodBundlePath, + DigestAlgorithm: "sha512", + OIDCIssuer: "some issuer", + Owner: "sigstore", + } + + opts.SetPolicyFlags() + require.Equal(t, "sigstore", opts.Owner) + require.Equal(t, "^https://github.com/sigstore/", opts.SANRegex) + }) + + t.Run("does not set SANRegex when SANRegex and Owner are provided", func(t *testing.T) { + opts := Options{ + ArtifactPath: publicGoodArtifactPath, + BundlePath: publicGoodBundlePath, + DigestAlgorithm: "sha512", + OIDCIssuer: "some issuer", + Owner: "sigstore", + SANRegex: "^https://github/foo", + } + + opts.SetPolicyFlags() + require.Equal(t, "sigstore", opts.Owner) + require.Equal(t, "^https://github/foo", opts.SANRegex) + }) +} diff --git a/pkg/cmd/attestation/verify/policy.go b/pkg/cmd/attestation/verify/policy.go new file mode 100644 index 000000000..83d1f62a7 --- /dev/null +++ b/pkg/cmd/attestation/verify/policy.go @@ -0,0 +1,85 @@ +package verify + +import ( + "fmt" + + "github.com/sigstore/sigstore-go/pkg/fulcio/certificate" + "github.com/sigstore/sigstore-go/pkg/verify" + + "github.com/cli/cli/v2/pkg/cmd/attestation/artifact" + "github.com/cli/cli/v2/pkg/cmd/attestation/verification" +) + +const ( + GitHubOIDCIssuer = "https://token.actions.githubusercontent.com" + SLSAPredicateType = "https://slsa.dev/provenance/v1" + // represents the GitHub hosted runner in the certificate RunnerEnvironment extension + GitHubRunner = "github-hosted" +) + +func buildSANMatcher(san, sanRegex string) (verify.SubjectAlternativeNameMatcher, error) { + if san == "" && sanRegex == "" { + return verify.SubjectAlternativeNameMatcher{}, nil + } + + sanMatcher, err := verify.NewSANMatcher(san, "", sanRegex) + if err != nil { + return verify.SubjectAlternativeNameMatcher{}, err + } + return sanMatcher, nil +} + +func buildCertificateIdentityOption(opts *Options, runnerEnv string) (verify.PolicyOption, error) { + sanMatcher, err := buildSANMatcher(opts.SAN, opts.SANRegex) + if err != nil { + return nil, err + } + + extensions := certificate.Extensions{ + Issuer: opts.OIDCIssuer, + SourceRepositoryOwnerURI: fmt.Sprintf("https://github.com/%s", opts.Owner), + RunnerEnvironment: runnerEnv, + } + certId, err := verify.NewCertificateIdentity(sanMatcher, extensions) + if err != nil { + return nil, err + } + + return verify.WithCertificateIdentity(certId), nil +} + +func buildVerifyCertIdOption(opts *Options) (verify.PolicyOption, error) { + if opts.DenySelfHostedRunner { + withGHRunner, err := buildCertificateIdentityOption(opts, GitHubRunner) + if err != nil { + return nil, err + } + + return withGHRunner, nil + } + + // if Extensions.RunnerEnvironment value is set to the empty string + // through the second function argument, + // no certificate matching will happen on the RunnerEnvironment field + withAnyRunner, err := buildCertificateIdentityOption(opts, "") + if err != nil { + return nil, err + } + + return withAnyRunner, nil +} + +func buildVerifyPolicy(opts *Options, a artifact.DigestedArtifact) (verify.PolicyBuilder, error) { + artifactDigestPolicyOption, err := verification.BuildDigestPolicyOption(a) + if err != nil { + return verify.PolicyBuilder{}, err + } + + certIdOption, err := buildVerifyCertIdOption(opts) + if err != nil { + return verify.PolicyBuilder{}, err + } + + policy := verify.NewPolicy(artifactDigestPolicyOption, certIdOption) + return policy, nil +} diff --git a/pkg/cmd/attestation/verify/policy_test.go b/pkg/cmd/attestation/verify/policy_test.go new file mode 100644 index 000000000..f15144314 --- /dev/null +++ b/pkg/cmd/attestation/verify/policy_test.go @@ -0,0 +1,31 @@ +package verify + +import ( + "testing" + + "github.com/cli/cli/v2/pkg/cmd/attestation/artifact" + "github.com/cli/cli/v2/pkg/cmd/attestation/artifact/oci" + + "github.com/stretchr/testify/require" +) + +// This tests that a policy can be built from a valid artifact +// Note that policy use is tested in verify_test.go in this package +func TestBuildPolicy(t *testing.T) { + ociClient := oci.MockClient{} + artifactPath := "../test/data/sigstore-js-2.1.0.tgz" + digestAlg := "sha256" + + artifact, err := artifact.NewDigestedArtifact(ociClient, artifactPath, digestAlg) + require.NoError(t, err) + + opts := &Options{ + ArtifactPath: artifactPath, + OIDCIssuer: GitHubOIDCIssuer, + Owner: "sigstore", + SANRegex: "^https://github.com/sigstore/", + } + + _, err = buildVerifyPolicy(opts, *artifact) + require.NoError(t, err) +} diff --git a/pkg/cmd/attestation/verify/verify.go b/pkg/cmd/attestation/verify/verify.go new file mode 100644 index 000000000..718f893a2 --- /dev/null +++ b/pkg/cmd/attestation/verify/verify.go @@ -0,0 +1,216 @@ +package verify + +import ( + // "encoding/json" + "errors" + "fmt" + + "github.com/cli/cli/v2/pkg/cmd/attestation/api" + "github.com/cli/cli/v2/pkg/cmd/attestation/artifact" + "github.com/cli/cli/v2/pkg/cmd/attestation/artifact/oci" + "github.com/cli/cli/v2/pkg/cmd/attestation/auth" + "github.com/cli/cli/v2/pkg/cmd/attestation/io" + "github.com/cli/cli/v2/pkg/cmd/attestation/verification" + "github.com/cli/cli/v2/pkg/cmdutil" + + "github.com/MakeNowJust/heredoc" + "github.com/spf13/cobra" +) + +var ErrNoMatchingSLSAPredicate = fmt.Errorf("the attestation does not have the expected SLSA predicate type: %s", SLSAPredicateType) + +func NewVerifyCmd(f *cmdutil.Factory, runF func(*Options) error) *cobra.Command { + opts := &Options{} + verifyCmd := &cobra.Command{ + Use: "verify [ | oci://] [--owner | --repo]", + Args: cobra.ExactArgs(1), + Short: "Verify an artifact's integrity using attestations", + Long: heredoc.Docf(` + Verify the integrity and provenance of an artifact using its associated + cryptographically signed attestations. + + The command requires either: + * a file path to an artifact, or + * a container image URI (e.g. %[1]soci://%[1]s) + + (Note that if you provide an OCI URL, you must already be authenticated with + its container registry.) + + In addition, the command requires either: + * the %[1]s--owner%[1]s flag (e.g. --owner github), or + * the %[1]s--repo%[1]s flag (e.g. --repo github/example). + + The %[1]s--owner%[1]s flag value must match the name of the GitHub organization + that the artifact is associated with. + + The %[1]s--repo%[1]s flag value must match the name of the GitHub repository + that the artifact is associated with. + + By default, the verify command will attempt to fetch attestations associated + with the provided artifact from the GitHub API. If you would prefer to verify + the artifact using attestations stored on disk (i.e. from the download command), + provide a path to the %[1]s--bundle%[1]s flag. + + To see the full results that are generated upon successful verification, i.e. + for use with a policy engine, provide the %[1]s--json-result%[1]s flag. + + For more policy verification options, see the other available flags. + `, "`"), + Example: heredoc.Doc(` + # Verify a local artifact associated with a repository + $ gh attestation verify example.bin --repo github/example + + # Verify a local artifact associated with an organization + $ gh attestation verify example.bin --owner github + + # Verify an OCI image using locally stored attestations + $ gh attestation verify oci:// --owner github --bundle sha256:foo.jsonl + `), + // PreRunE is used to validate flags before the command is run + // If an error is returned, its message will be printed to the terminal + // along with information about how use the command + PreRunE: func(cmd *cobra.Command, args []string) error { + // Create a logger for use throughout the verify command + opts.Logger = io.NewHandler(f.IOStreams) + + // set the artifact path + opts.ArtifactPath = args[0] + + // Check that the given flag combination is valid + if err := opts.AreFlagsValid(); err != nil { + return err + } + + // Clean file path options + opts.Clean() + + // set policy flags based on what has been provided + opts.SetPolicyFlags() + + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + hc, err := f.HttpClient() + if err != nil { + return err + } + opts.APIClient = api.NewLiveClient(hc, opts.Logger) + + opts.OCIClient = oci.NewLiveClient() + + if err := auth.IsHostSupported(); err != nil { + return err + } + + if runF != nil { + return runF(opts) + } + + if err := runVerify(opts); err != nil { + return fmt.Errorf("Failed to verify the artifact: %v", err) + } + return nil + }, + } + + // general flags + verifyCmd.Flags().StringVarP(&opts.BundlePath, "bundle", "b", "", "Path to bundle on disk, either a single bundle in a JSON file or a JSON lines file with multiple bundles") + cmdutil.StringEnumFlag(verifyCmd, &opts.DigestAlgorithm, "digest-alg", "d", "sha256", []string{"sha256", "sha512"}, "The algorithm used to compute a digest of the artifact") + verifyCmd.Flags().StringVarP(&opts.Owner, "owner", "o", "", "GitHub organization to scope attestation lookup by") + verifyCmd.Flags().StringVarP(&opts.Repo, "repo", "R", "", "Repository name in the format /") + verifyCmd.MarkFlagsMutuallyExclusive("owner", "repo") + verifyCmd.MarkFlagsOneRequired("owner", "repo") + verifyCmd.Flags().BoolVarP(&opts.NoPublicGood, "no-public-good", "", false, "Only verify attestations signed with GitHub's Sigstore instance") + verifyCmd.Flags().StringVarP(&opts.CustomTrustedRoot, "custom-trusted-root", "", "", "Path to a custom trustedroot.json file to use for verification") + verifyCmd.Flags().IntVarP(&opts.Limit, "limit", "L", api.DefaultLimit, "Maximum number of attestations to fetch") + cmdutil.AddFormatFlags(verifyCmd, &opts.exporter) + // policy enforcement flags + verifyCmd.Flags().BoolVarP(&opts.DenySelfHostedRunner, "deny-self-hosted-runners", "", false, "Fail verification for attestations generated on self-hosted runners.") + verifyCmd.Flags().StringVarP(&opts.SAN, "cert-identity", "", "", "Enforce that the certificate's subject alternative name matches the provided value exactly") + verifyCmd.Flags().StringVarP(&opts.SANRegex, "cert-identity-regex", "i", "", "Enforce that the certificate's subject alternative name matches the provided regex") + verifyCmd.MarkFlagsMutuallyExclusive("cert-identity", "cert-identity-regex") + verifyCmd.Flags().StringVarP(&opts.OIDCIssuer, "cert-oidc-issuer", "", GitHubOIDCIssuer, "Issuer of the OIDC token") + + return verifyCmd +} + +func runVerify(opts *Options) error { + artifact, err := artifact.NewDigestedArtifact(opts.OCIClient, opts.ArtifactPath, opts.DigestAlgorithm) + if err != nil { + return fmt.Errorf("failed to digest artifact: %s", err) + } + + opts.Logger.Printf("Verifying attestations for the artifact found at %s\n", artifact.URL) + + c := verification.FetchAttestationsConfig{ + APIClient: opts.APIClient, + BundlePath: opts.BundlePath, + Digest: artifact.DigestWithAlg(), + Limit: opts.Limit, + Owner: opts.Owner, + Repo: opts.Repo, + } + attestations, err := verification.GetAttestations(c) + if err != nil { + if ok := errors.Is(err, api.ErrNoAttestations{}); ok { + return fmt.Errorf("no attestations found for subject: %s", artifact.DigestWithAlg()) + } + return fmt.Errorf("failed to fetch attestations for subject: %s", artifact.DigestWithAlg()) + } + + policy, err := buildVerifyPolicy(opts, *artifact) + if err != nil { + return fmt.Errorf("failed to build policy: %v", err) + } + + config := verification.SigstoreConfig{ + CustomTrustedRoot: opts.CustomTrustedRoot, + Logger: opts.Logger, + NoPublicGood: opts.NoPublicGood, + } + + sv, err := verification.NewSigstoreVerifier(config, policy) + if err != nil { + return err + } + + sigstoreRes := sv.Verify(attestations) + if sigstoreRes.Error != nil { + return fmt.Errorf("at least one attestation failed to verify against Sigstore: %v", sigstoreRes.Error) + } + + opts.Logger.VerbosePrint(opts.Logger.ColorScheme.Green( + "Successfully verified all attestations against Sigstore!\n", + )) + + // Try verifying the attestation's predicate type against the expect SLSA predicate type + if err = verifySLSAPredicateType(opts.Logger, sigstoreRes.VerifyResults); err != nil { + return fmt.Errorf("at least one attestation failed to verify predicate type verification: %v", err) + } + + opts.Logger.VerbosePrint(opts.Logger.ColorScheme.Green("Successfully verified the SLSA predicate type of all attestations!\n")) + + opts.Logger.Println(opts.Logger.ColorScheme.Green("All attestations have been successfully verified!")) + + if opts.exporter != nil { + // print the results to the terminal as an array of JSON objects + if err = opts.exporter.Write(opts.Logger.IO, sigstoreRes.VerifyResults); err != nil { + return fmt.Errorf("failed to write JSON output") + } + } + + // All attestations passed verification and policy evaluation + return nil +} + +func verifySLSAPredicateType(logger *io.Handler, apr []*verification.AttestationProcessingResult) error { + logger.VerbosePrint("Evaluating attestations have valid SLSA predicate type") + + for _, result := range apr { + if result.VerificationResult.Statement.PredicateType != SLSAPredicateType { + return ErrNoMatchingSLSAPredicate + } + } + + return nil +} diff --git a/pkg/cmd/attestation/verify/verify_test.go b/pkg/cmd/attestation/verify/verify_test.go new file mode 100644 index 000000000..b4cd864fc --- /dev/null +++ b/pkg/cmd/attestation/verify/verify_test.go @@ -0,0 +1,401 @@ +package verify + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "strings" + "testing" + + "github.com/cli/cli/v2/pkg/cmd/attestation/api" + "github.com/cli/cli/v2/pkg/cmd/attestation/artifact/oci" + "github.com/cli/cli/v2/pkg/cmd/attestation/io" + "github.com/cli/cli/v2/pkg/cmd/attestation/test" + "github.com/cli/cli/v2/pkg/cmd/attestation/verification" + "github.com/cli/cli/v2/pkg/cmdutil" + + "github.com/cli/cli/v2/pkg/httpmock" + "github.com/cli/cli/v2/pkg/iostreams" + "github.com/stretchr/testify/assert" + + "github.com/in-toto/in-toto-golang/in_toto" + "github.com/sigstore/sigstore-go/pkg/verify" + + "github.com/stretchr/testify/require" +) + +const ( + SigstoreSanValue = "https://github.com/sigstore/sigstore-js/.github/workflows/release.yml@refs/heads/main" + SigstoreSanRegex = "^https://github.com/sigstore/sigstore-js/" +) + +var ( + artifactPath = test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0.tgz") + bundlePath = test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0-bundle.json") +) + +func TestNewVerifyCmd(t *testing.T) { + testIO, _, _, _ := iostreams.Test() + f := &cmdutil.Factory{ + IOStreams: testIO, + HttpClient: func() (*http.Client, error) { + reg := &httpmock.Registry{} + client := &http.Client{} + httpmock.ReplaceTripper(client, reg) + return client, nil + }, + } + + testcases := []struct { + name string + cli string + wants Options + wantsErr bool + wantsExporter bool + }{ + { + name: "Invalid digest-alg flag", + cli: fmt.Sprintf("%s --bundle %s --digest-alg sha384 --owner sigstore", artifactPath, bundlePath), + wants: Options{ + ArtifactPath: test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0.tgz"), + BundlePath: test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0-bundle.json"), + DigestAlgorithm: "sha384", + Limit: 30, + OIDCIssuer: GitHubOIDCIssuer, + Owner: "sigstore", + }, + wantsErr: true, + }, + { + name: "Use default digest-alg value", + cli: fmt.Sprintf("%s --bundle %s --owner sigstore", artifactPath, bundlePath), + wants: Options{ + ArtifactPath: test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0.tgz"), + BundlePath: test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0-bundle.json"), + DigestAlgorithm: "sha256", + Limit: 30, + OIDCIssuer: GitHubOIDCIssuer, + Owner: "sigstore", + SANRegex: "^https://github.com/sigstore/", + }, + wantsErr: false, + }, + { + name: "Use custom digest-alg value", + cli: fmt.Sprintf("%s --bundle %s --owner sigstore --digest-alg sha512", artifactPath, bundlePath), + wants: Options{ + ArtifactPath: test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0.tgz"), + BundlePath: test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0-bundle.json"), + DigestAlgorithm: "sha512", + Limit: 30, + OIDCIssuer: GitHubOIDCIssuer, + Owner: "sigstore", + SANRegex: "^https://github.com/sigstore/", + }, + wantsErr: false, + }, + { + name: "Missing owner and repo flags", + cli: artifactPath, + wants: Options{ + ArtifactPath: test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0.tgz"), + DigestAlgorithm: "sha256", + OIDCIssuer: GitHubOIDCIssuer, + Owner: "sigstore", + Limit: 30, + SANRegex: "^https://github.com/sigstore/", + }, + wantsErr: true, + }, + { + name: "Has both owner and repo flags", + cli: fmt.Sprintf("%s --owner sigstore --repo sigstore/sigstore-js", artifactPath), + wants: Options{ + ArtifactPath: artifactPath, + DigestAlgorithm: "sha256", + OIDCIssuer: GitHubOIDCIssuer, + Owner: "sigstore", + Repo: "sigstore/sigstore-js", + Limit: 30, + }, + wantsErr: true, + }, + { + name: "Uses default limit flag", + cli: fmt.Sprintf("%s --owner sigstore", artifactPath), + wants: Options{ + ArtifactPath: artifactPath, + DigestAlgorithm: "sha256", + Limit: 30, + OIDCIssuer: GitHubOIDCIssuer, + Owner: "sigstore", + SANRegex: "^https://github.com/sigstore/", + }, + wantsErr: false, + }, + { + name: "Uses custom limit flag", + cli: fmt.Sprintf("%s --owner sigstore --limit 101", artifactPath), + wants: Options{ + ArtifactPath: artifactPath, + DigestAlgorithm: "sha256", + OIDCIssuer: GitHubOIDCIssuer, + Owner: "sigstore", + Limit: 101, + SANRegex: "^https://github.com/sigstore/", + }, + wantsErr: false, + }, + { + name: "Uses invalid limit flag", + cli: fmt.Sprintf("%s --owner sigstore --limit 0", artifactPath), + wants: Options{ + ArtifactPath: artifactPath, + DigestAlgorithm: "sha256", + OIDCIssuer: GitHubOIDCIssuer, + Owner: "sigstore", + Limit: 0, + SANRegex: "^https://github.com/sigstore/", + }, + wantsErr: true, + }, + { + name: "Has both cert-identity and cert-identity-regex flags", + cli: fmt.Sprintf("%s --owner sigstore --cert-identity https://github.com/sigstore/ --cert-identity-regex ^https://github.com/sigstore/", artifactPath), + wants: Options{ + ArtifactPath: artifactPath, + DigestAlgorithm: "sha256", + Limit: 30, + OIDCIssuer: GitHubOIDCIssuer, + Owner: "sigstore", + SAN: "https://github.com/sigstore/", + SANRegex: "^https://github.com/sigstore/", + }, + wantsErr: true, + }, + { + name: "Prints output in JSON format", + cli: fmt.Sprintf("%s --bundle %s --owner sigstore --format json", artifactPath, bundlePath), + wants: Options{ + ArtifactPath: artifactPath, + BundlePath: bundlePath, + DigestAlgorithm: "sha256", + Limit: 30, + OIDCIssuer: GitHubOIDCIssuer, + Owner: "sigstore", + SANRegex: "^https://github.com/sigstore/", + }, + wantsExporter: true, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + var opts *Options + cmd := NewVerifyCmd(f, func(o *Options) error { + opts = o + return nil + }) + + argv := strings.Split(tc.cli, " ") + cmd.SetArgs(argv) + cmd.SetIn(&bytes.Buffer{}) + cmd.SetOut(&bytes.Buffer{}) + cmd.SetErr(&bytes.Buffer{}) + _, err := cmd.ExecuteC() + if tc.wantsErr { + assert.Error(t, err) + return + } + assert.NoError(t, err) + + assert.Equal(t, tc.wants.ArtifactPath, opts.ArtifactPath) + assert.Equal(t, tc.wants.BundlePath, opts.BundlePath) + assert.Equal(t, tc.wants.CustomTrustedRoot, opts.CustomTrustedRoot) + assert.Equal(t, tc.wants.DenySelfHostedRunner, opts.DenySelfHostedRunner) + assert.Equal(t, tc.wants.DigestAlgorithm, opts.DigestAlgorithm) + assert.Equal(t, tc.wants.Limit, opts.Limit) + assert.Equal(t, tc.wants.NoPublicGood, opts.NoPublicGood) + assert.Equal(t, tc.wants.OIDCIssuer, opts.OIDCIssuer) + assert.Equal(t, tc.wants.Owner, opts.Owner) + assert.Equal(t, tc.wants.Repo, opts.Repo) + assert.Equal(t, tc.wants.SAN, opts.SAN) + assert.Equal(t, tc.wants.SANRegex, opts.SANRegex) + assert.NotNil(t, opts.APIClient) + assert.NotNil(t, opts.Logger) + assert.NotNil(t, opts.OCIClient) + assert.Equal(t, tc.wantsExporter, opts.exporter != nil) + }) + } +} + +func TestJSONOutput(t *testing.T) { + testIO, _, out, _ := iostreams.Test() + opts := Options{ + ArtifactPath: artifactPath, + BundlePath: bundlePath, + DigestAlgorithm: "sha512", + APIClient: api.NewTestClient(), + Logger: io.NewHandler(testIO), + OCIClient: oci.MockClient{}, + OIDCIssuer: GitHubOIDCIssuer, + Owner: "sigstore", + SANRegex: "^https://github.com/sigstore/", + exporter: cmdutil.NewJSONExporter(), + } + require.Nil(t, runVerify(&opts)) + + var target []*verification.AttestationProcessingResult + err := json.Unmarshal(out.Bytes(), &target) + require.NoError(t, err) +} + +func TestRunVerify(t *testing.T) { + logger := io.NewTestHandler() + + publicGoodOpts := Options{ + ArtifactPath: artifactPath, + BundlePath: bundlePath, + DigestAlgorithm: "sha512", + APIClient: api.NewTestClient(), + Logger: logger, + OCIClient: oci.MockClient{}, + OIDCIssuer: GitHubOIDCIssuer, + Owner: "sigstore", + SANRegex: "^https://github.com/sigstore/", + } + + t.Run("with valid artifact and bundle", func(t *testing.T) { + require.Nil(t, runVerify(&publicGoodOpts)) + }) + + t.Run("with failing OCI artifact fetch", func(t *testing.T) { + opts := publicGoodOpts + opts.ArtifactPath = "oci://ghcr.io/github/test" + opts.OCIClient = oci.ReferenceFailClient{} + + err := runVerify(&opts) + require.Error(t, err) + require.ErrorContains(t, err, "failed to digest artifact") + }) + + t.Run("with missing artifact path", func(t *testing.T) { + opts := publicGoodOpts + opts.ArtifactPath = "../test/data/non-existent-artifact.zip" + require.Error(t, runVerify(&opts)) + }) + + t.Run("with missing bundle path", func(t *testing.T) { + opts := publicGoodOpts + opts.BundlePath = "../test/data/non-existent-sigstoreBundle.json" + require.Error(t, runVerify(&opts)) + }) + + t.Run("with owner", func(t *testing.T) { + opts := publicGoodOpts + opts.BundlePath = "" + opts.Owner = "sigstore" + + require.Nil(t, runVerify(&opts)) + }) + + t.Run("with repo", func(t *testing.T) { + opts := publicGoodOpts + opts.BundlePath = "" + opts.Repo = "github/example" + + require.Nil(t, runVerify(&opts)) + }) + + t.Run("with invalid repo", func(t *testing.T) { + opts := publicGoodOpts + opts.BundlePath = "" + opts.Repo = "wrong/example" + opts.APIClient = api.NewFailTestClient() + + err := runVerify(&opts) + require.Error(t, err) + require.ErrorContains(t, err, "failed to fetch attestations for subject") + }) + + t.Run("with invalid owner", func(t *testing.T) { + opts := publicGoodOpts + opts.BundlePath = "" + opts.APIClient = api.NewFailTestClient() + opts.Owner = "wrong-owner" + + err := runVerify(&opts) + require.Error(t, err) + require.ErrorContains(t, err, "failed to fetch attestations for subject") + }) + + t.Run("with invalid OIDC issuer", func(t *testing.T) { + opts := publicGoodOpts + opts.OIDCIssuer = "not-a-real-issuer" + require.Error(t, runVerify(&opts)) + }) + + t.Run("with SAN enforcement", func(t *testing.T) { + opts := Options{ + ArtifactPath: artifactPath, + BundlePath: bundlePath, + APIClient: api.NewTestClient(), + DigestAlgorithm: "sha512", + Logger: logger, + OIDCIssuer: GitHubOIDCIssuer, + Owner: "sigstore", + SAN: SigstoreSanValue, + } + require.Nil(t, runVerify(&opts)) + }) + + t.Run("with invalid SAN", func(t *testing.T) { + opts := publicGoodOpts + opts.SAN = "fake san" + require.Error(t, runVerify(&opts)) + }) + + t.Run("with SAN regex enforcement", func(t *testing.T) { + opts := publicGoodOpts + opts.SANRegex = SigstoreSanRegex + require.Nil(t, runVerify(&opts)) + }) + + t.Run("with invalid SAN regex", func(t *testing.T) { + opts := publicGoodOpts + opts.SANRegex = "^https://github.com/sigstore/not-real/" + require.Error(t, runVerify(&opts)) + }) + + t.Run("with no matching OIDC issuer", func(t *testing.T) { + opts := publicGoodOpts + opts.OIDCIssuer = "some-other-issuer" + + require.Error(t, runVerify(&opts)) + }) + + t.Run("with missing API client", func(t *testing.T) { + customOpts := publicGoodOpts + customOpts.APIClient = nil + customOpts.BundlePath = "" + require.Error(t, runVerify(&customOpts)) + }) +} + +func TestVerifySLSAPredicateType_InvalidPredicate(t *testing.T) { + statement := &in_toto.Statement{} + statement.PredicateType = "some-other-predicate-type" + + apr := []*verification.AttestationProcessingResult{ + { + VerificationResult: &verify.VerificationResult{ + Statement: statement, + }, + }, + } + + err := verifySLSAPredicateType(io.NewTestHandler(), apr) + require.Error(t, err) + require.ErrorIs(t, err, ErrNoMatchingSLSAPredicate) +} diff --git a/pkg/cmd/root/root.go b/pkg/cmd/root/root.go index db591c27b..4b744e192 100644 --- a/pkg/cmd/root/root.go +++ b/pkg/cmd/root/root.go @@ -10,6 +10,7 @@ import ( aliasCmd "github.com/cli/cli/v2/pkg/cmd/alias" "github.com/cli/cli/v2/pkg/cmd/alias/shared" apiCmd "github.com/cli/cli/v2/pkg/cmd/api" + attestationCmd "github.com/cli/cli/v2/pkg/cmd/attestation" authCmd "github.com/cli/cli/v2/pkg/cmd/auth" browseCmd "github.com/cli/cli/v2/pkg/cmd/browse" cacheCmd "github.com/cli/cli/v2/pkg/cmd/cache" @@ -124,6 +125,7 @@ func NewCmdRoot(f *cmdutil.Factory, version, buildDate string) (*cobra.Command, cmd.AddCommand(actionsCmd.NewCmdActions(f)) cmd.AddCommand(aliasCmd.NewCmdAlias(f)) cmd.AddCommand(authCmd.NewCmdAuth(f)) + cmd.AddCommand(attestationCmd.NewCmdAttestation(f)) cmd.AddCommand(configCmd.NewCmdConfig(f)) cmd.AddCommand(creditsCmd.NewCmdCredits(f, nil)) cmd.AddCommand(gistCmd.NewCmdGist(f)) diff --git a/test/integration/attestation-cmd/download-and-verify-package-attestation.sh b/test/integration/attestation-cmd/download-and-verify-package-attestation.sh new file mode 100755 index 000000000..6774b8e2d --- /dev/null +++ b/test/integration/attestation-cmd/download-and-verify-package-attestation.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Get the root directory of the repository +rootDir="$(git rev-parse --show-toplevel)" + +ghBuildPath="$rootDir/bin/gh" + +# Compute the package and attestation URLs +labRatPackageName="sigstore" +latestPackageVersion=$(npm -s info $labRatPackageName dist-tags.latest | tr -d '\n') +packageFile="$labRatPackageName-$latestPackageVersion.tgz" +packageURL="https://registry.npmjs.org/$labRatPackageName/-/$packageFile" +attestationFile="$labRatPackageName-$latestPackageVersion.json" +attestationURL="https://registry.npmjs.org/-/npm/v1/attestations/$labRatPackageName@$latestPackageVersion" + +echo "Testing with package $packageFile and attestation $attestationFile" + +curl -s "$packageURL" -o "$packageFile" +curl -s "$attestationURL" | jq '.attestations[1].bundle' > "$attestationFile" + +# Verify the package with the --owner flag +if ! $ghBuildPath attestation verify "$packageFile" -b "$attestationFile" --digest-alg=sha512 --owner=sigstore; then + # cleanup test data + echo "Failed to verify package with --owner flag" + rm "$packageFile" "$attestationFile" + exit 1 +fi + +if ! $ghBuildPath attestation verify "$packageFile" -b "$attestationFile" --digest-alg=sha512 --repo=sigstore/sigstore-js; then + # cleanup test data + echo "Failed to verify package with --repo flag" + rm "$packageFile" "$attestationFile" + exit 1 +fi + +# cleanup test data +rm "$packageFile" "$attestationFile" From acb5b9bd425bdcfeb80cc113f22c0774d08d4605 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 17:14:59 +0000 Subject: [PATCH 12/26] build(deps): bump gopkg.in/go-jose/go-jose.v2 from 2.6.1 to 2.6.3 Bumps gopkg.in/go-jose/go-jose.v2 from 2.6.1 to 2.6.3. --- updated-dependencies: - dependency-name: gopkg.in/go-jose/go-jose.v2 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index e419c3224..29fa7e10e 100644 --- a/go.mod +++ b/go.mod @@ -160,7 +160,7 @@ require ( google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac // indirect - gopkg.in/go-jose/go-jose.v2 v2.6.1 // indirect + gopkg.in/go-jose/go-jose.v2 v2.6.3 // indirect gopkg.in/ini.v1 v1.67.0 // indirect k8s.io/klog/v2 v2.120.0 // indirect ) diff --git a/go.sum b/go.sum index ac71cdba3..6a19810ec 100644 --- a/go.sum +++ b/go.sum @@ -568,8 +568,8 @@ google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHh gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/go-jose/go-jose.v2 v2.6.1 h1:qEzJlIDmG9q5VO0M/o8tGS65QMHMS1w01TQJB1VPJ4U= -gopkg.in/go-jose/go-jose.v2 v2.6.1/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI= +gopkg.in/go-jose/go-jose.v2 v2.6.3 h1:nt80fvSDlhKWQgSWyHyy5CfmlQr+asih51R8PTWNKKs= +gopkg.in/go-jose/go-jose.v2 v2.6.3/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI= gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY= gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= From 82254f7d49a729e9a9c29a4c660b4d1256ac0f48 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 17:15:19 +0000 Subject: [PATCH 13/26] build(deps): bump github.com/docker/docker Bumps [github.com/docker/docker](https://github.com/docker/docker) from 24.0.7+incompatible to 24.0.9+incompatible. - [Release notes](https://github.com/docker/docker/releases) - [Commits](https://github.com/docker/docker/compare/v24.0.7...v24.0.9) --- updated-dependencies: - dependency-name: github.com/docker/docker dependency-type: indirect ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index e419c3224..822f77be8 100644 --- a/go.mod +++ b/go.mod @@ -69,7 +69,7 @@ require ( github.com/dlclark/regexp2 v1.4.0 // indirect github.com/docker/cli v24.0.0+incompatible // indirect github.com/docker/distribution v2.8.2+incompatible // indirect - github.com/docker/docker v24.0.7+incompatible // indirect + github.com/docker/docker v24.0.9+incompatible // indirect github.com/docker/docker-credential-helpers v0.7.0 // indirect github.com/fatih/color v1.14.1 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect diff --git a/go.sum b/go.sum index ac71cdba3..6d856106f 100644 --- a/go.sum +++ b/go.sum @@ -130,8 +130,8 @@ github.com/docker/cli v24.0.0+incompatible h1:0+1VshNwBQzQAx9lOl+OYCTCEAD8fKs/qe github.com/docker/cli v24.0.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM= -github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v24.0.9+incompatible h1:HPGzNmwfLZWdxHqK9/II92pyi1EpYKsAqcl4G0Of9v0= +github.com/docker/docker v24.0.9+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= From 5d79a95a32338b858f44d567584d98cbc90b64de Mon Sep 17 00:00:00 2001 From: Naoya Yasuda <43776161+yanskun@users.noreply.github.com> Date: Tue, 2 Apr 2024 21:34:59 +0900 Subject: [PATCH 14/26] Upgrade to Go 1.22 (#8836) --- .devcontainer/devcontainer.json | 2 +- .github/CONTRIBUTING.md | 2 +- .github/workflows/codeql.yml | 5 +++++ .github/workflows/deployment.yml | 2 +- .github/workflows/go.yml | 8 ++++---- .github/workflows/lint.yml | 4 ++-- api/http_client_test.go | 2 +- docs/source.md | 2 +- go.mod | 2 +- 9 files changed, 17 insertions(+), 12 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index e69a804ee..20bf83882 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,5 +1,5 @@ { - "image": "mcr.microsoft.com/devcontainers/go:1.21", + "image": "mcr.microsoft.com/devcontainers/go:1.22", "features": { "ghcr.io/devcontainers/features/sshd:1": {} }, diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 73174db65..9f5d56376 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -24,7 +24,7 @@ Please avoid: ## Building the project Prerequisites: -- Go 1.21+ +- Go 1.22+ Build with: * Unix-like systems: `make` diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index c45c65e34..00c45a957 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -29,5 +29,10 @@ jobs: languages: go queries: security-and-quality + - name: Setup go + uses: actions/setup-go@v5 + with: + go-version: '1.22' + - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index bf138c95d..6f3f42494 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -18,7 +18,7 @@ on: default: production type: environment go_version: - default: "1.21" + default: "1.22" type: string platforms: default: "linux,macos,windows" diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 7284a9e08..fa6001369 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -13,10 +13,10 @@ jobs: runs-on: ${{ matrix.os }} steps: - - name: Set up Go 1.21 + - name: Set up Go 1.22 uses: actions/setup-go@v5 with: - go-version: 1.21 + go-version: 1.22 - name: Check out code uses: actions/checkout@v4 @@ -48,10 +48,10 @@ jobs: runs-on: ${{ matrix.os }} steps: - - name: Set up Go 1.21 + - name: Set up Go 1.22 uses: actions/setup-go@v5 with: - go-version: 1.21 + go-version: 1.22 - name: Check out code uses: actions/checkout@v4 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 57f584e00..67069277c 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -19,10 +19,10 @@ jobs: runs-on: ubuntu-latest steps: - - name: Set up Go 1.21 + - name: Set up Go 1.22 uses: actions/setup-go@v5 with: - go-version: 1.21 + go-version: 1.22 - name: Check out code uses: actions/checkout@v4 diff --git a/api/http_client_test.go b/api/http_client_test.go index dca032e6f..ce20a2684 100644 --- a/api/http_client_test.go +++ b/api/http_client_test.go @@ -222,7 +222,7 @@ func TestHTTPClientSanitizeJSONControlCharactersC0(t *testing.T) { err = json.Unmarshal(body, &issue) require.NoError(t, err) assert.Equal(t, "^[[31mRed Title^[[0m", issue.Title) - assert.Equal(t, "1^A 2^B 3^C 4^D 5^E 6^F 7^G 8^H 9\t A\r\n B\v C^L D\r\n E^N F^O", issue.Body) + assert.Equal(t, "1^A 2^B 3^C 4^D 5^E 6^F 7^G 8\b 9\t A\r\n B\v C\f D\r\n E^N F^O", issue.Body) assert.Equal(t, "10^P 11^Q 12^R 13^S 14^T 15^U 16^V 17^W 18^X 19^Y 1A^Z 1B^[ 1C^\\ 1D^] 1E^^ 1F^_", issue.Author.Name) assert.Equal(t, "monalisa \\u00^[", issue.Author.Login) assert.Equal(t, "Escaped ^[ \\^[ \\^[ \\\\^[", issue.ActiveLockReason) diff --git a/docs/source.md b/docs/source.md index cc1605b8c..d2b7c5b31 100644 --- a/docs/source.md +++ b/docs/source.md @@ -1,6 +1,6 @@ # Installation from source -1. Verify that you have Go 1.21+ installed +1. Verify that you have Go 1.22+ installed ```sh $ go version diff --git a/go.mod b/go.mod index e419c3224..fe6701037 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/cli/cli/v2 -go 1.21 +go 1.22 require ( github.com/AlecAivazis/survey/v2 v2.3.7 From 200c95e5b2471b1d6b73fa882ae21881b1a9f596 Mon Sep 17 00:00:00 2001 From: William Martin Date: Tue, 2 Apr 2024 15:22:31 +0200 Subject: [PATCH 15/26] Rely on go.mod go version in all workflows (#8911) --- .github/workflows/codeql.yml | 4 ++-- .github/workflows/deployment.yml | 11 ++++------- .github/workflows/go.yml | 20 ++++++++++---------- .github/workflows/lint.yml | 10 +++++----- 4 files changed, 21 insertions(+), 24 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 00c45a957..8cd5ecbee 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -29,10 +29,10 @@ jobs: languages: go queries: security-and-quality - - name: Setup go + - name: Setup Go uses: actions/setup-go@v5 with: - go-version: '1.22' + go-version-file: 'go.mod' - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index 6f3f42494..51059a669 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -1,5 +1,5 @@ name: Deployment -run-name: ${{ inputs.tag_name }} / go ${{ inputs.go_version }} / ${{ inputs.environment }} +run-name: ${{ inputs.tag_name }} / ${{ inputs.environment }} concurrency: group: ${{ github.workflow }}-${{ github.ref_name }} @@ -17,9 +17,6 @@ on: environment: default: production type: environment - go_version: - default: "1.22" - type: string platforms: default: "linux,macos,windows" type: string @@ -39,7 +36,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: ${{ inputs.go_version }} + go-version-file: 'go.mod' - name: Install GoReleaser uses: goreleaser/goreleaser-action@v5 with: @@ -73,7 +70,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: ${{ inputs.go_version }} + go-version-file: 'go.mod' - name: Configure macOS signing if: inputs.environment == 'production' env: @@ -130,7 +127,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: ${{ inputs.go_version }} + go-version-file: 'go.mod' - name: Install GoReleaser uses: goreleaser/goreleaser-action@v5 with: diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index fa6001369..1dded32b0 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -13,14 +13,14 @@ jobs: runs-on: ${{ matrix.os }} steps: - - name: Set up Go 1.22 - uses: actions/setup-go@v5 - with: - go-version: 1.22 - - name: Check out code uses: actions/checkout@v4 + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: 'go.mod' + - name: Restore Go modules cache uses: actions/cache@v4 with: @@ -48,14 +48,14 @@ jobs: runs-on: ${{ matrix.os }} steps: - - name: Set up Go 1.22 - uses: actions/setup-go@v5 - with: - go-version: 1.22 - - name: Check out code uses: actions/checkout@v4 + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: 'go.mod' + - name: Build executable run: make diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 67069277c..52f99cd81 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -19,14 +19,14 @@ jobs: runs-on: ubuntu-latest steps: - - name: Set up Go 1.22 - uses: actions/setup-go@v5 - with: - go-version: 1.22 - - name: Check out code uses: actions/checkout@v4 + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: 'go.mod' + - name: Restore Go modules cache uses: actions/cache@v4 with: From 228c31f20529af144dfd5e0ecfcde8e8c9f9a24a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Apr 2024 14:11:27 +0000 Subject: [PATCH 16/26] build(deps): bump google.golang.org/grpc from 1.61.0 to 1.61.1 Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.61.0 to 1.61.1. - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.61.0...v1.61.1) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ad25d835a..f986a3373 100644 --- a/go.mod +++ b/go.mod @@ -45,7 +45,7 @@ require ( golang.org/x/sync v0.6.0 golang.org/x/term v0.17.0 golang.org/x/text v0.14.0 - google.golang.org/grpc v1.61.0 + google.golang.org/grpc v1.61.1 google.golang.org/protobuf v1.33.0 gopkg.in/h2non/gock.v1 v1.1.2 gopkg.in/yaml.v3 v3.0.1 diff --git a/go.sum b/go.sum index db514ab67..432df4d91 100644 --- a/go.sum +++ b/go.sum @@ -559,8 +559,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe h1: google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac h1:nUQEQmH/csSvFECKYRv6HWEyypysidKl2I6Qpsglq/0= google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA= -google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0= -google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= +google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY= +google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= From f66b2fc6eb528c4ff1f1a561eac4b42f474c5e23 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Apr 2024 14:11:31 +0000 Subject: [PATCH 17/26] build(deps): bump github.com/gorilla/websocket from 1.5.0 to 1.5.1 Bumps [github.com/gorilla/websocket](https://github.com/gorilla/websocket) from 1.5.0 to 1.5.1. - [Release notes](https://github.com/gorilla/websocket/releases) - [Commits](https://github.com/gorilla/websocket/compare/v1.5.0...v1.5.1) --- updated-dependencies: - dependency-name: github.com/gorilla/websocket dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ad25d835a..53c26878a 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/google/go-cmp v0.6.0 github.com/google/go-containerregistry v0.19.0 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 - github.com/gorilla/websocket v1.5.0 + github.com/gorilla/websocket v1.5.1 github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-version v1.3.0 github.com/henvic/httpretty v0.1.3 diff --git a/go.sum b/go.sum index db514ab67..e68e76ca7 100644 --- a/go.sum +++ b/go.sum @@ -216,8 +216,8 @@ github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56 github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= +github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= From 47f190f2b559592e14b7da52dea86dc6f3099b1e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Apr 2024 15:30:53 +0000 Subject: [PATCH 18/26] build(deps): bump github.com/google/go-containerregistry Bumps [github.com/google/go-containerregistry](https://github.com/google/go-containerregistry) from 0.19.0 to 0.19.1. - [Release notes](https://github.com/google/go-containerregistry/releases) - [Changelog](https://github.com/google/go-containerregistry/blob/main/.goreleaser.yml) - [Commits](https://github.com/google/go-containerregistry/compare/v0.19.0...v0.19.1) --- updated-dependencies: - dependency-name: github.com/google/go-containerregistry dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 108e45707..bb1885569 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/gabriel-vasile/mimetype v1.4.3 github.com/gdamore/tcell/v2 v2.5.4 github.com/google/go-cmp v0.6.0 - github.com/google/go-containerregistry v0.19.0 + github.com/google/go-containerregistry v0.19.1 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/gorilla/websocket v1.5.1 github.com/hashicorp/go-multierror v1.1.1 diff --git a/go.sum b/go.sum index 918c3be66..92e305222 100644 --- a/go.sum +++ b/go.sum @@ -196,8 +196,8 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/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-containerregistry v0.19.0 h1:uIsMRBV7m/HDkDxE/nXMnv1q+lOOSPlQ/ywc5JbB8Ic= -github.com/google/go-containerregistry v0.19.0/go.mod h1:u0qB2l7mvtWVR5kNcbFIhFY1hLbf8eeGapA+vbFDCtQ= +github.com/google/go-containerregistry v0.19.1 h1:yMQ62Al6/V0Z7CqIrrS1iYoA5/oQCm88DeNujc7C1KY= +github.com/google/go-containerregistry v0.19.1/go.mod h1:YCMFNQeeXeLF+dnhhWkqDItx/JSkH01j1Kis4PsjzFI= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= From 83f200d91fdf00b0e84d1294f49d51b633c20332 Mon Sep 17 00:00:00 2001 From: William Martin Date: Tue, 2 Apr 2024 20:08:19 +0200 Subject: [PATCH 19/26] Bump glamour to v0.7.0 and go mod tidy --- go.mod | 12 ++++++------ go.sum | 35 +++++++++++++++++++---------------- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/go.mod b/go.mod index bb1885569..e5bf28636 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/MakeNowJust/heredoc v1.0.0 github.com/briandowns/spinner v1.18.1 github.com/cenkalti/backoff/v4 v4.2.1 - github.com/charmbracelet/glamour v0.6.0 + github.com/charmbracelet/glamour v0.7.0 github.com/charmbracelet/lipgloss v0.5.0 github.com/cli/go-gh/v2 v2.7.0 github.com/cli/oauth v1.0.1 @@ -52,10 +52,10 @@ require ( ) require ( - github.com/alecthomas/chroma v0.10.0 // indirect + github.com/alecthomas/chroma/v2 v2.8.0 // indirect github.com/alessio/shellescape v1.4.2 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect - github.com/aymanbagabas/go-osc52 v1.0.3 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/blang/semver v3.5.1+incompatible // indirect github.com/cli/browser v1.3.0 // indirect @@ -112,7 +112,7 @@ require ( github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/muesli/reflow v0.3.0 // indirect - github.com/muesli/termenv v0.13.0 // indirect + github.com/muesli/termenv v0.15.2 // indirect github.com/oklog/ulid v1.3.1 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect @@ -145,8 +145,8 @@ require ( github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect github.com/transparency-dev/merkle v0.0.2 // indirect github.com/vbatts/tar-split v0.11.3 // indirect - github.com/yuin/goldmark v1.5.2 // indirect - github.com/yuin/goldmark-emoji v1.0.1 // indirect + github.com/yuin/goldmark v1.5.4 // indirect + github.com/yuin/goldmark-emoji v1.0.2 // indirect go.mongodb.org/mongo-driver v1.13.1 // indirect go.opentelemetry.io/otel v1.22.0 // indirect go.opentelemetry.io/otel/metric v1.22.0 // indirect diff --git a/go.sum b/go.sum index 92e305222..f48022a2b 100644 --- a/go.sum +++ b/go.sum @@ -30,8 +30,12 @@ github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= -github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek= -github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= +github.com/alecthomas/assert/v2 v2.2.1 h1:XivOgYcduV98QCahG8T5XTezV5bylXe+lBxLG2K2ink= +github.com/alecthomas/assert/v2 v2.2.1/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= +github.com/alecthomas/chroma/v2 v2.8.0 h1:w9WJUjFFmHHB2e8mRpL9jjy3alYDlU0QLDezj1xE264= +github.com/alecthomas/chroma/v2 v2.8.0/go.mod h1:yrkMI9807G1ROx13fhe1v6PN2DDeaR73L3d+1nmYQtw= +github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk= +github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/alessio/shellescape v1.4.2 h1:MHPfaU+ddJ0/bYWpgIeUnQUqKrlJ1S7BfEYPM4uEoM0= github.com/alessio/shellescape v1.4.2/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= @@ -66,8 +70,8 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 h1:NzO4Vrau795RkUdSHKEwiR01FaGz github.com/aws/aws-sdk-go-v2/service/sts v1.26.7/go.mod h1:6h2YuIoxaMSCFf5fi1EgZAwdfkGMgDY+DVfa61uLe4U= github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM= github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE= -github.com/aymanbagabas/go-osc52 v1.0.3 h1:DTwqENW7X9arYimJrPeGZcV0ln14sGMt3pHZspWD+Mg= -github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= 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/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -82,8 +86,8 @@ github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqy github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/charmbracelet/glamour v0.6.0 h1:wi8fse3Y7nfcabbbDuwolqTqMQPMnVPeZhDM273bISc= -github.com/charmbracelet/glamour v0.6.0/go.mod h1:taqWV4swIMMbWALc0m7AfE9JkPSU8om2538k9ITBxOc= +github.com/charmbracelet/glamour v0.7.0 h1:2BtKGZ4iVJCDfMF229EzbeR1QRKLWztO9dMtjmqZSng= +github.com/charmbracelet/glamour v0.7.0/go.mod h1:jUMh5MeihljJPQbJ/wf4ldw2+yBP59+ctV36jASy7ps= github.com/charmbracelet/lipgloss v0.5.0 h1:lulQHuVeodSgDez+3rGiuxlPVXSnhth442DATR2/8t8= github.com/charmbracelet/lipgloss v0.5.0/go.mod h1:EZLha/HbzEt7cYqdFPovlqy5FZPj0xFhg5SaqxScmgs= github.com/cli/browser v1.0.0/go.mod h1:IEWkHYbLjkhtjwwWlwTHW2lGxeS5gezEQBMLTwDHf5Q= @@ -248,6 +252,8 @@ github.com/hashicorp/vault/api v1.10.0 h1:/US7sIjWN6Imp4o/Rj1Ce2Nr5bki/AXi9vAW3p github.com/hashicorp/vault/api v1.10.0/go.mod h1:jo5Y/ET+hNyz+JnKDt8XLAdKs+AM0G5W0Vp1IrFI8N8= github.com/henvic/httpretty v0.1.3 h1:4A6vigjz6Q/+yAfTD4wqipCv+Px69C7Th/NhT0ApuU8= github.com/henvic/httpretty v0.1.3/go.mod h1:UUEv7c2kHZ5SPQ51uS3wBpzPDibg2U3Y+IaXyHy5GBg= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog= github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef h1:A9HsByNhogrvm9cWb28sjiS3i7tcKCkflWFEkHfuAgM= @@ -311,7 +317,6 @@ github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQth github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= -github.com/microcosm-cc/bluemonday v1.0.21/go.mod h1:ytNkv4RrDrLJ2pqlsSI46O6IVXmZOBBD4SaJyDwwTkM= github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58= github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs= github.com/microsoft/dev-tunnels v0.0.25 h1:UlMKUI+2O8cSu4RlB52ioSyn1LthYSVkJA+CSTsdKoA= @@ -325,8 +330,8 @@ github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIW github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= -github.com/muesli/termenv v0.13.0 h1:wK20DRpJdDX8b7Ek2QfhvqhRQFZ237RGRO0RQ/Iqdy0= -github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc= +github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= +github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= github.com/muhammadmuzzammil1998/jsonc v0.0.0-20201229145248-615b0916ca38 h1:0FrBxrkJ0hVembTb/e4EU5Ml6vLcOusAqymmYISg5Uo= github.com/muhammadmuzzammil1998/jsonc v0.0.0-20201229145248-615b0916ca38/go.mod h1:saF2fIVw4banK0H4+/EuqfFLpRnoy5S+ECwTOCcRcSU= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4= @@ -455,12 +460,12 @@ github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.7/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yuin/goldmark v1.5.2 h1:ALmeCk/px5FSm1MAcFBAsVKZjDuMVj8Tm7FFIlMJnqU= -github.com/yuin/goldmark v1.5.2/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yuin/goldmark-emoji v1.0.1 h1:ctuWEyzGBwiucEqxzwe0SOYDXPAucOrE9NQC18Wa1os= -github.com/yuin/goldmark-emoji v1.0.1/go.mod h1:2w1E6FEWLcDQkoTE+7HU6QF1F6SLlNGjRIBbIZQFqkQ= +github.com/yuin/goldmark v1.5.4 h1:2uY/xC0roWy8IBEGLgB1ywIoEJFGmRrX21YQcvGZzjU= +github.com/yuin/goldmark v1.5.4/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark-emoji v1.0.2 h1:c/RgTShNgHTtc6xdz2KKI74jJr6rWi7FPgnP9GAsO5s= +github.com/yuin/goldmark-emoji v1.0.2/go.mod h1:RhP/RWpexdp+KHs7ghKnifRoIs/Bq4nDS7tRbCkOwKY= github.com/zalando/go-keyring v0.2.4 h1:wi2xxTqdiwMKbM6TWwi+uJCG/Tum2UV0jqaQhCa9/68= github.com/zalando/go-keyring v0.2.4/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk= go.mongodb.org/mongo-driver v1.13.1 h1:YIc7HTYsKndGK4RFzJ3covLz1byri52x0IoMB0Pt/vk= @@ -501,7 +506,6 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= @@ -520,7 +524,6 @@ golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220906165534-d0df966e6959/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= From 72fcd8c87d57692f339f1ccefa7c1eb445db4a0f Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Mon, 1 Apr 2024 21:40:26 +0100 Subject: [PATCH 20/26] Improve error message returned by `FindByRepo` Signed-off-by: Babak K. Shandiz --- context/remote.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/context/remote.go b/context/remote.go index 3540ad95d..6094179f4 100644 --- a/context/remote.go +++ b/context/remote.go @@ -31,7 +31,7 @@ func (r Remotes) FindByRepo(owner, name string) (*Remote, error) { return rem, nil } } - return nil, fmt.Errorf("no matching remote found") + return nil, fmt.Errorf("no matching remote found; looking for %s/%s", owner, name) } // Filter remotes by given hostnames, maintains original order From eb24a5b2411163835053bbddd19ed2816cff6302 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Apr 2024 14:20:03 +0000 Subject: [PATCH 21/26] build(deps): bump github.com/sigstore/protobuf-specs from 0.3.0 to 0.3.1 Bumps [github.com/sigstore/protobuf-specs](https://github.com/sigstore/protobuf-specs) from 0.3.0 to 0.3.1. - [Release notes](https://github.com/sigstore/protobuf-specs/releases) - [Changelog](https://github.com/sigstore/protobuf-specs/blob/main/CHANGELOG.md) - [Commits](https://github.com/sigstore/protobuf-specs/compare/v0.3.0...v0.3.1) --- updated-dependencies: - dependency-name: github.com/sigstore/protobuf-specs dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index bb1885569..da13b3eba 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,7 @@ require ( github.com/opentracing/opentracing-go v1.2.0 github.com/rivo/tview v0.0.0-20221029100920-c4a7e501810d github.com/shurcooL/githubv4 v0.0.0-20230704064427-599ae7bbf278 - github.com/sigstore/protobuf-specs v0.3.0 + github.com/sigstore/protobuf-specs v0.3.1 github.com/sigstore/sigstore-go v0.2.1-0.20240222221148-8bd2a8139edc github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 diff --git a/go.sum b/go.sum index 92e305222..94072d4bd 100644 --- a/go.sum +++ b/go.sum @@ -390,8 +390,8 @@ github.com/shurcooL/githubv4 v0.0.0-20230704064427-599ae7bbf278 h1:kdEGVAV4sO46D github.com/shurcooL/githubv4 v0.0.0-20230704064427-599ae7bbf278/go.mod h1:zqMwyHmnN/eDOZOdiTohqIUKUrTFX62PNlu7IJdu0q8= github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 h1:17JxqqJY66GmZVHkmAsGEkcIu0oCe3AM420QDgGwZx0= github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466/go.mod h1:9dIRpgIY7hVhoqfe0/FcYp0bpInZaT7dc3BYOprrIUE= -github.com/sigstore/protobuf-specs v0.3.0 h1:E49qS++llp4psM+3NNVEb+C4AD422bT9VkOQIPrNLpA= -github.com/sigstore/protobuf-specs v0.3.0/go.mod h1:ynKzXpqr3dUj2Xk9O/5ZUhjnpi0F53DNi5AdH6pS3jc= +github.com/sigstore/protobuf-specs v0.3.1 h1:9aJQrPq7iRDSLBNg//zsP7tAzxdHnD1sA+1FyCCrkrQ= +github.com/sigstore/protobuf-specs v0.3.1/go.mod h1:HfkcPi5QXteuew4+c5ONz8vYQ8aOH//ZTQ3gg0X8ZUA= github.com/sigstore/rekor v1.3.5 h1:QoVXcS7NppKY+rpbEFVHr4evGDZBBSh65X0g8PXoUkQ= github.com/sigstore/rekor v1.3.5/go.mod h1:CWqOk/fmnPwORQmm7SyDgB54GTJizqobbZ7yOP1lvw8= github.com/sigstore/sigstore v1.8.1 h1:mAVposMb14oplk2h/bayPmIVdzbq2IhCgy4g6R0ZSjo= From 32882db3e2eabe398afff76a55c8b97a916b855f Mon Sep 17 00:00:00 2001 From: William Martin Date: Wed, 3 Apr 2024 17:12:06 +0200 Subject: [PATCH 22/26] Bump go-gh to v2.8.0 and go mod tidy --- go.mod | 8 ++++---- go.sum | 15 ++++++++------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index e5bf28636..25ec826ab 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/cenkalti/backoff/v4 v4.2.1 github.com/charmbracelet/glamour v0.7.0 github.com/charmbracelet/lipgloss v0.5.0 - github.com/cli/go-gh/v2 v2.7.0 + github.com/cli/go-gh/v2 v2.8.0 github.com/cli/oauth v1.0.1 github.com/cli/safeexec v1.0.1 github.com/cpuguy83/go-md2man/v2 v2.0.4 @@ -98,7 +98,7 @@ require ( github.com/hashicorp/go-retryablehttp v0.7.5 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/itchyny/gojq v0.12.13 // indirect + github.com/itchyny/gojq v0.12.15 // indirect github.com/itchyny/timefmt-go v0.1.5 // indirect github.com/jedisct1/go-minisign v0.0.0-20211028175153-1c139d1cc84b // indirect github.com/josharian/intern v1.0.0 // indirect @@ -107,7 +107,7 @@ require ( github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect - github.com/mattn/go-runewidth v0.0.14 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect github.com/microcosm-cc/bluemonday v1.0.26 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect @@ -120,7 +120,7 @@ require ( github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/rivo/uniseg v0.4.4 // indirect + github.com/rivo/uniseg v0.4.7 // indirect github.com/rodaine/table v1.0.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect diff --git a/go.sum b/go.sum index f48022a2b..938452b04 100644 --- a/go.sum +++ b/go.sum @@ -93,8 +93,8 @@ github.com/charmbracelet/lipgloss v0.5.0/go.mod h1:EZLha/HbzEt7cYqdFPovlqy5FZPj0 github.com/cli/browser v1.0.0/go.mod h1:IEWkHYbLjkhtjwwWlwTHW2lGxeS5gezEQBMLTwDHf5Q= github.com/cli/browser v1.3.0 h1:LejqCrpWr+1pRqmEPDGnTZOjsMe7sehifLynZJuqJpo= github.com/cli/browser v1.3.0/go.mod h1:HH8s+fOAxjhQoBUAsKuPCbqUuxZDhQ2/aD+SzsEfBTk= -github.com/cli/go-gh/v2 v2.7.0 h1:7PfL5uBQ1e3TOj3V1qU4U/kmTSondi8Q1uyvH72ARiM= -github.com/cli/go-gh/v2 v2.7.0/go.mod h1:h3salfqqooVpzKmHp6aUdeNx62UmxQRpLbagFSHTJGQ= +github.com/cli/go-gh/v2 v2.8.0 h1:xFDgnhRiVMFanqACszdyRduUaFkoMOiasOrox0sKpKo= +github.com/cli/go-gh/v2 v2.8.0/go.mod h1:U6GukjNwSqD6coCKP6FTp/yrPk5ttNYjAe+ZYSOFCVc= github.com/cli/oauth v1.0.1 h1:pXnTFl/qUegXHK531Dv0LNjW4mLx626eS42gnzfXJPA= github.com/cli/oauth v1.0.1/go.mod h1:qd/FX8ZBD6n1sVNQO3aIdRxeu5LGw9WhKnYhIIoC2A4= github.com/cli/safeexec v1.0.0/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q= @@ -262,8 +262,8 @@ github.com/in-toto/in-toto-golang v0.9.0 h1:tHny7ac4KgtsfrG6ybU8gVOZux2H8jN05AXJ github.com/in-toto/in-toto-golang v0.9.0/go.mod h1:xsBVrVsHNsB61++S6Dy2vWosKhuA3lUTQd+eF9HdeMo= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/itchyny/gojq v0.12.13 h1:IxyYlHYIlspQHHTE0f3cJF0NKDMfajxViuhBLnHd/QU= -github.com/itchyny/gojq v0.12.13/go.mod h1:JzwzAqenfhrPUuwbmEz3nu3JQmFLlQTQMUcOdnu/Sf4= +github.com/itchyny/gojq v0.12.15 h1:WC1Nxbx4Ifw5U2oQWACYz32JK8G9qxNtHzrvW4KEcqI= +github.com/itchyny/gojq v0.12.15/go.mod h1:uWAHCbCIla1jiNxmeT5/B5mOjSdfkCq6p8vxWg+BM10= github.com/itchyny/timefmt-go v0.1.5 h1:G0INE2la8S6ru/ZI5JecgyzbbJNs5lG1RcBqa7Jm6GE= github.com/itchyny/timefmt-go v0.1.5/go.mod h1:nEP7L+2YmAbT2kZ2HfSs1d8Xtw9LY8D2stDBckWakZ8= github.com/jedisct1/go-minisign v0.0.0-20211028175153-1c139d1cc84b h1:ZGiXF8sz7PDk6RgkP+A/SFfUD0ZR/AgG6SpRNEDKZy8= @@ -309,8 +309,9 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +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/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= @@ -367,8 +368,8 @@ github.com/rivo/tview v0.0.0-20221029100920-c4a7e501810d h1:jKIUJdMcIVGOSHi6LSqJ github.com/rivo/tview v0.0.0-20221029100920-c4a7e501810d/go.mod h1:YX2wUZOcJGOIycErz2s9KvDaP0jnWwRCirQMPLPpQ+Y= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= -github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rodaine/table v1.0.1 h1:U/VwCnUxlVYxw8+NJiLIuCxA/xa6jL38MY3FYysVWWQ= github.com/rodaine/table v1.0.1/go.mod h1:UVEtfBsflpeEcD56nF4F5AocNFta0ZuolpSVdPtlmP4= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= From 642b2c57c127a8a1f9a3724776834b078f88d88c Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Thu, 4 Apr 2024 00:19:15 +0100 Subject: [PATCH 23/26] Add tests for `FindByRepo` Signed-off-by: Babak K. Shandiz --- context/remote_test.go | 65 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/context/remote_test.go b/context/remote_test.go index 7db0473a0..d57e2e1e1 100644 --- a/context/remote_test.go +++ b/context/remote_test.go @@ -28,6 +28,71 @@ func Test_Remotes_FindByName(t *testing.T) { assert.Error(t, err, "no GitHub remotes found") } +func Test_Remotes_FindByRepo(t *testing.T) { + list := Remotes{ + &Remote{Remote: &git.Remote{Name: "remote-0"}, Repo: ghrepo.New("owner", "repo")}, + &Remote{Remote: &git.Remote{Name: "remote-1"}, Repo: ghrepo.New("another-owner", "another-repo")}, + } + + tests := []struct { + name string + owner string + repo string + wantsRemote *Remote + wantsError string + }{ + { + name: "exact match (owner/repo)", + owner: "owner", + repo: "repo", + wantsRemote: list[0], + }, + { + name: "exact match (another-owner/another-repo)", + owner: "another-owner", + repo: "another-repo", + wantsRemote: list[1], + }, + { + name: "case-insensitive match", + owner: "OWNER", + repo: "REPO", + wantsRemote: list[0], + }, + { + name: "non-match (owner)", + owner: "unknown-owner", + repo: "repo", + wantsError: "no matching remote found; looking for unknown-owner/repo", + }, + { + name: "non-match (repo)", + owner: "owner", + repo: "unknown-repo", + wantsError: "no matching remote found; looking for owner/unknown-repo", + }, + { + name: "non-match (owner, repo)", + owner: "unknown-owner", + repo: "unknown-repo", + wantsError: "no matching remote found; looking for unknown-owner/unknown-repo", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r, err := list.FindByRepo(tt.owner, tt.repo) + if tt.wantsError != "" { + assert.Error(t, err, tt.wantsError) + assert.Nil(t, r) + } else { + assert.NoError(t, err) + assert.Equal(t, r, tt.wantsRemote) + } + }) + } +} + type identityTranslator struct{} func (it identityTranslator) Translate(u *url.URL) *url.URL { From dd0a827ce5b7b485e982a02b91f5198932f3facb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Apr 2024 14:38:42 +0000 Subject: [PATCH 24/26] build(deps): bump google.golang.org/grpc from 1.61.1 to 1.61.2 Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.61.1 to 1.61.2. - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.61.1...v1.61.2) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ba96c7cb0..e779ae75f 100644 --- a/go.mod +++ b/go.mod @@ -45,7 +45,7 @@ require ( golang.org/x/sync v0.6.0 golang.org/x/term v0.17.0 golang.org/x/text v0.14.0 - google.golang.org/grpc v1.61.1 + google.golang.org/grpc v1.61.2 google.golang.org/protobuf v1.33.0 gopkg.in/h2non/gock.v1 v1.1.2 gopkg.in/yaml.v3 v3.0.1 diff --git a/go.sum b/go.sum index 1c759cbeb..5cff3f2cf 100644 --- a/go.sum +++ b/go.sum @@ -563,8 +563,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe h1: google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac h1:nUQEQmH/csSvFECKYRv6HWEyypysidKl2I6Qpsglq/0= google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA= -google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY= -google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= +google.golang.org/grpc v1.61.2 h1:TzJay21lXCf7BiNFKl7mSskt5DlkKAumAYTs52SpJeo= +google.golang.org/grpc v1.61.2/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= From 2c53d7c9a823c85ba1ac9960c157a631c3fad47a Mon Sep 17 00:00:00 2001 From: Meredith Lancaster Date: Fri, 5 Apr 2024 01:41:34 -0600 Subject: [PATCH 25/26] Add codeowners entry for the GitHub TUF root included in the `attestation` command set (#8919) Signed-off-by: Meredith Lancaster --- .github/CODEOWNERS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 5aad3e925..8464d95a5 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -5,3 +5,5 @@ internal/codespaces/ @cli/codespaces # Limit Package Security team ownership to the attestation command package pkg/cmd/attestation/ @cli/package-security + +pkg/cmd/attestation/verification/embed/tuf-repo.github.com/ @cli/tuf-root-reviewers From 47efef6d70123fdbc3d212068dc32ce86ceed591 Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Fri, 5 Apr 2024 10:54:55 +0100 Subject: [PATCH 26/26] Fix parsing IPv6 remote URLs (#8893) Signed-off-by: Babak K. Shandiz --- git/url.go | 4 +--- git/url_test.go | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/git/url.go b/git/url.go index 1a3e97fd6..890ccbe1c 100644 --- a/git/url.go +++ b/git/url.go @@ -56,9 +56,7 @@ func ParseURL(rawURL string) (u *url.URL, err error) { u.Path = strings.TrimPrefix(u.Path, "/") } - if idx := strings.Index(u.Host, ":"); idx >= 0 { - u.Host = u.Host[0:idx] - } + u.Host = strings.TrimSuffix(u.Host, ":"+u.Port()) return } diff --git a/git/url_test.go b/git/url_test.go index f5b3b50d0..d31c1b817 100644 --- a/git/url_test.go +++ b/git/url_test.go @@ -126,6 +126,26 @@ func TestParseURL(t *testing.T) { Path: "/owner/repo.git", }, }, + { + name: "ssh, ipv6", + url: "ssh://git@[::1]/owner/repo.git", + want: url{ + Scheme: "ssh", + User: "git", + Host: "[::1]", + Path: "/owner/repo.git", + }, + }, + { + name: "ssh with port, ipv6", + url: "ssh://git@[::1]:22/owner/repo.git", + want: url{ + Scheme: "ssh", + User: "git", + Host: "[::1]", + Path: "/owner/repo.git", + }, + }, { name: "git+ssh", url: "git+ssh://example.com/owner/repo.git",