add additional packages to support verify command
Signed-off-by: Meredith Lancaster <malancas@github.com>
This commit is contained in:
parent
82bd13b127
commit
501add44c0
31 changed files with 1477 additions and 210 deletions
|
|
@ -1,4 +1,4 @@
|
|||
package github
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
98
pkg/cmd/attestation/api/client.go
Normal file
98
pkg/cmd/attestation/api/client.go
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/cli/cli/v2/api"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultLimit = 30
|
||||
maxLimitForFlag = 1000
|
||||
maxLimitForFetch = 100
|
||||
)
|
||||
|
||||
type apiClient interface {
|
||||
REST(hostname, method, p string, body io.Reader, data interface{}) error
|
||||
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 {
|
||||
host string
|
||||
api apiClient
|
||||
}
|
||||
|
||||
func NewLiveClient() *LiveClient {
|
||||
liveAPIClient := api.NewClientFromHTTP(http.DefaultClient)
|
||||
return &LiveClient{
|
||||
host: "https://api.github.com",
|
||||
api: liveAPIClient,
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
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(resp.Attestations) == 0 {
|
||||
return nil, newErrNoAttestations(name, digest)
|
||||
}
|
||||
|
||||
if len(attestations) > limit {
|
||||
return attestations[:limit], nil
|
||||
}
|
||||
|
||||
return attestations, nil
|
||||
}
|
||||
176
pkg/cmd/attestation/api/client_test.go
Normal file
176
pkg/cmd/attestation/api/client_test.go
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
testRepo = "github/example"
|
||||
testOwner = "github"
|
||||
testDigest = "sha256:12313213"
|
||||
)
|
||||
|
||||
func NewClientWithMockGHClient() Client {
|
||||
fetcher := mockDataGenerator{
|
||||
NumAttestations: 5,
|
||||
}
|
||||
|
||||
return &LiveClient{
|
||||
api: mockAPIClient{
|
||||
OnREST: fetcher.OnRESTSuccess,
|
||||
OnRESTWithNext: fetcher.OnRESTSuccessWithNextPage,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func NewClientWithMockGHClientWithNextPage() Client {
|
||||
fetcher := mockDataGenerator{
|
||||
NumAttestations: 5,
|
||||
}
|
||||
|
||||
return &LiveClient{
|
||||
api: mockAPIClient{
|
||||
OnREST: fetcher.OnRESTSuccess,
|
||||
OnRESTWithNext: fetcher.OnRESTSuccessWithNextPage,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
assert.Equal(t, data.expected, s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetByDigest(t *testing.T) {
|
||||
c := NewClientWithMockGHClient()
|
||||
attestations, err := c.GetByRepoAndDigest(testRepo, testDigest, DefaultLimit)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, len(attestations), 5)
|
||||
bundle := (attestations)[0].Bundle
|
||||
assert.Equal(t, bundle.GetMediaType(), "application/vnd.dev.sigstore.bundle+json;version=0.1")
|
||||
|
||||
attestations, err = c.GetByOwnerAndDigest(testOwner, testDigest, DefaultLimit)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, len(attestations), 5)
|
||||
bundle = (attestations)[0].Bundle
|
||||
assert.Equal(t, bundle.GetMediaType(), "application/vnd.dev.sigstore.bundle+json;version=0.1")
|
||||
}
|
||||
|
||||
func TestGetByDigestGreaterThanLimit(t *testing.T) {
|
||||
c := NewClientWithMockGHClient()
|
||||
|
||||
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)
|
||||
|
||||
assert.Equal(t, len(attestations), 3)
|
||||
bundle := (attestations)[0].Bundle
|
||||
assert.Equal(t, bundle.GetMediaType(), "application/vnd.dev.sigstore.bundle+json;version=0.1")
|
||||
|
||||
attestations, err = c.GetByOwnerAndDigest(testOwner, testDigest, limit)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, len(attestations), limit)
|
||||
bundle = (attestations)[0].Bundle
|
||||
assert.Equal(t, bundle.GetMediaType(), "application/vnd.dev.sigstore.bundle+json;version=0.1")
|
||||
}
|
||||
|
||||
func TestGetByDigestWithNextPage(t *testing.T) {
|
||||
c := NewClientWithMockGHClientWithNextPage()
|
||||
attestations, err := c.GetByRepoAndDigest(testRepo, testDigest, DefaultLimit)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, len(attestations), 10)
|
||||
bundle := (attestations)[0].Bundle
|
||||
assert.Equal(t, bundle.GetMediaType(), "application/vnd.dev.sigstore.bundle+json;version=0.1")
|
||||
|
||||
attestations, err = c.GetByOwnerAndDigest(testOwner, testDigest, DefaultLimit)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, len(attestations), 10)
|
||||
bundle = (attestations)[0].Bundle
|
||||
assert.Equal(t, bundle.GetMediaType(), "application/vnd.dev.sigstore.bundle+json;version=0.1")
|
||||
}
|
||||
|
||||
func TestGetByDigestGreaterThanLimitWithNextPage(t *testing.T) {
|
||||
c := NewClientWithMockGHClientWithNextPage()
|
||||
|
||||
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)
|
||||
|
||||
assert.Equal(t, len(attestations), limit)
|
||||
bundle := (attestations)[0].Bundle
|
||||
assert.Equal(t, bundle.GetMediaType(), "application/vnd.dev.sigstore.bundle+json;version=0.1")
|
||||
|
||||
attestations, err = c.GetByOwnerAndDigest(testOwner, testDigest, limit)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, len(attestations), limit)
|
||||
bundle = (attestations)[0].Bundle
|
||||
assert.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{
|
||||
OnREST: fetcher.OnRESTNoAttestations,
|
||||
OnRESTWithNext: fetcher.OnRESTSuccessWithNextPage,
|
||||
},
|
||||
}
|
||||
|
||||
attestations, err := c.GetByRepoAndDigest(testRepo, testDigest, DefaultLimit)
|
||||
assert.Error(t, err)
|
||||
assert.IsType(t, ErrNoAttestations{}, err)
|
||||
assert.Nil(t, attestations)
|
||||
|
||||
attestations, err = c.GetByOwnerAndDigest(testOwner, testDigest, DefaultLimit)
|
||||
assert.Error(t, err)
|
||||
assert.IsType(t, ErrNoAttestations{}, err)
|
||||
assert.Nil(t, attestations)
|
||||
}
|
||||
|
||||
func TestGetByDigest_Error(t *testing.T) {
|
||||
fetcher := mockDataGenerator{
|
||||
NumAttestations: 5,
|
||||
}
|
||||
|
||||
c := LiveClient{
|
||||
api: mockAPIClient{
|
||||
OnREST: fetcher.OnRESTError,
|
||||
OnRESTWithNext: fetcher.OnRESTSuccessWithNextPage,
|
||||
},
|
||||
}
|
||||
|
||||
attestations, err := c.GetByRepoAndDigest(testRepo, testDigest, DefaultLimit)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, attestations)
|
||||
|
||||
attestations, err = c.GetByOwnerAndDigest(testOwner, testDigest, DefaultLimit)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, attestations)
|
||||
}
|
||||
71
pkg/cmd/attestation/api/mock-client.go
Normal file
71
pkg/cmd/attestation/api/mock-client.go
Normal file
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
152
pkg/cmd/attestation/api/mock_apiClient_test.go
Normal file
152
pkg/cmd/attestation/api/mock_apiClient_test.go
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type mockAPIClient struct {
|
||||
OnREST func(hostname, method, p string, body io.Reader, data interface{}) error
|
||||
OnRESTWithNext func(hostname, method, p string, body io.Reader, data interface{}) (string, error)
|
||||
}
|
||||
|
||||
func (m mockAPIClient) REST(hostname, method, p string, body io.Reader, data interface{}) error {
|
||||
return m.OnREST(hostname, method, p, body, data)
|
||||
}
|
||||
|
||||
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{}) error {
|
||||
return m.OnRESTSuccessHelper(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) OnRESTSuccessHelper(hostname, method, p string, body io.Reader, data interface{}, hasNext bool) error {
|
||||
atts := make([]*Attestation, m.NumAttestations)
|
||||
for j := 0; j < m.NumAttestations; j++ {
|
||||
att := makeTestAttestation()
|
||||
atts[j] = &att
|
||||
}
|
||||
|
||||
var resp AttestationsResponse
|
||||
resp.Attestations = atts
|
||||
|
||||
data = resp
|
||||
|
||||
// // Convert the attestations to JSON
|
||||
// jsonResponse, err := json.Marshal(resp)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// // Create a buffer containing the JSON response
|
||||
// responseReader := bytes.NewBuffer(jsonResponse)
|
||||
|
||||
// linkHeader := ""
|
||||
// if hasNext {
|
||||
// // Create a link header with the next page
|
||||
// linkHeader = fmt.Sprintf("<%s&after=2>; rel=\"next\"", p)
|
||||
// }
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
var resp AttestationsResponse
|
||||
resp.Attestations = atts
|
||||
|
||||
data = resp
|
||||
|
||||
// // Convert the attestations to JSON
|
||||
// jsonResponse, err := json.Marshal(resp)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// // Create a buffer containing the JSON response
|
||||
// responseReader := bytes.NewBuffer(jsonResponse)
|
||||
|
||||
// b, err := io.ReadAll(resp.Body)
|
||||
// if err != nil {
|
||||
// return "", err
|
||||
// }
|
||||
|
||||
// err = json.Unmarshal(b, &data)
|
||||
// if err != nil {
|
||||
// return "", err
|
||||
// }
|
||||
|
||||
|
||||
linkHeader := ""
|
||||
if hasNext {
|
||||
// Create a link header with the next page
|
||||
linkHeader = fmt.Sprintf("<%s&after=2>; rel=\"next\"", p)
|
||||
}
|
||||
|
||||
return linkHeader, nil
|
||||
}
|
||||
|
||||
func (m mockDataGenerator) OnRESTNoAttestations(hostname, method, p string, body io.Reader, data interface{}) error {
|
||||
var resp AttestationsResponse
|
||||
resp.Attestations = make([]*Attestation, 0)
|
||||
|
||||
data = resp
|
||||
|
||||
// Convert the attestations to JSON
|
||||
// jsonResponse, err := json.Marshal(resp)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// // Create a buffer containing the JSON response
|
||||
// responseReader := bytes.NewBuffer(jsonResponse)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m mockDataGenerator) OnRESTWithNextNoAttestations(hostname, method, p string, body io.Reader, data interface{}) (string, error) {
|
||||
var resp AttestationsResponse
|
||||
resp.Attestations = make([]*Attestation, 0)
|
||||
|
||||
data = resp
|
||||
|
||||
// Convert the attestations to JSON
|
||||
// data, err := json.Marshal(resp)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (m mockDataGenerator) OnRESTError(hostname, method, p string, body io.Reader, data interface{}) error {
|
||||
return errors.New("failed to get attestations")
|
||||
}
|
||||
|
||||
func (m mockDataGenerator) OnRESTWithNextError(hostname, method, p string, body io.Reader, data interface{}) (string, error) {
|
||||
return "", errors.New("failed to get attestations")
|
||||
}
|
||||
|
|
@ -63,11 +63,16 @@ func NewDigestedArtifact(client oci.Client, reference, digestAlg string) (artifa
|
|||
return digestLocalFileArtifact(normalized, digestAlg)
|
||||
}
|
||||
|
||||
// Digest returns the digest of the artifact
|
||||
// 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)
|
||||
|
|
|
|||
|
|
@ -2,8 +2,9 @@ package attestation
|
|||
|
||||
import (
|
||||
"github.com/cli/cli/v2/pkg/cmd/attestation/verify"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
package github
|
||||
|
||||
import "github.com/cli/cli/v2/api"
|
||||
|
||||
type Client interface {
|
||||
GetByRepoAndDigest(repo, digest string, limit int) ([]*Attestation, error)
|
||||
GetByOwnerAndDigest(owner, digest string, limit int) ([]*Attestation, error)
|
||||
}
|
||||
|
||||
type LiveClient struct {
|
||||
apiClient api.Client
|
||||
}
|
||||
|
||||
func NewLiveClient() (*LiveClient, error) {
|
||||
apiClient := api.NewClientFromHTTP(httpClient)
|
||||
return &LiveClient{
|
||||
apiClient: apiClient,
|
||||
}, nil
|
||||
}
|
||||
|
|
@ -2,21 +2,89 @@ package logger
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/go-gh/v2/pkg/tableprinter"
|
||||
)
|
||||
|
||||
type Logger struct {
|
||||
ColorScheme *iostreams.ColorScheme
|
||||
IO *iostreams.IOStreams
|
||||
Quiet bool
|
||||
Verbose bool
|
||||
quiet bool
|
||||
verbose bool
|
||||
}
|
||||
|
||||
func NewLogger(isQuiet, isVerbose bool) *Logger {
|
||||
io := iostreams.System()
|
||||
colorScheme := io.ColorScheme()
|
||||
return &Logger{
|
||||
ColorScheme: colorScheme,
|
||||
IO: io,
|
||||
quiet: isQuiet,
|
||||
verbose: isVerbose,
|
||||
}
|
||||
}
|
||||
|
||||
// NewDefaultLogger returns a Logger that with the default logging settings
|
||||
func NewDefaultLogger() *Logger {
|
||||
isQuiet := false
|
||||
isVerbose := false
|
||||
|
||||
return NewLogger(isQuiet, isVerbose)
|
||||
}
|
||||
|
||||
// Printf writes the formatted arguments to the stdout writer.
|
||||
func (l *Logger) Printf(f string, v ...interface{}) (int, error) {
|
||||
if l.quiet || !l.IO.IsStdoutTTY() {
|
||||
return 0, nil
|
||||
}
|
||||
return fmt.Fprintf(l.IO.ErrOut, f, v...)
|
||||
}
|
||||
|
||||
// Println writes the arguments to the stdout writer with a newline at the end.
|
||||
func (l *Logger) Println(v ...interface{}) (int, error) {
|
||||
if l.quiet || !l.IO.IsStdoutTTY() {
|
||||
return 0, nil
|
||||
}
|
||||
return fmt.Fprintln(l.IO.ErrOut, v...)
|
||||
}
|
||||
|
||||
func (l *Logger) VerbosePrint(msg string) (int, error) {
|
||||
if !l.verbose || !opts.IO.IsStdoutTTY() {
|
||||
if !l.verbose || !l.IO.IsStdoutTTY() {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
return fmt.Fprintf(l.IO.ErrOut, msg)
|
||||
}
|
||||
|
||||
func (l *Logger) VerbosePrintf(f string, v ...interface{}) (int, error) {
|
||||
if !l.verbose || !l.IO.IsStdoutTTY() {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
return fmt.Fprintf(l.IO.ErrOut, f, v...)
|
||||
}
|
||||
|
||||
func (l *Logger) PrintTableToStdOut(headers []string, rows [][]string) {
|
||||
if rows == nil {
|
||||
return
|
||||
}
|
||||
t := tableprinter.New(l.IO.Out, l.IO.IsStdoutTTY(), l.IO.TerminalWidth())
|
||||
|
||||
if headers != nil {
|
||||
// Print the header row in green
|
||||
t.AddHeader(headers, tableprinter.WithColor(l.ColorScheme.Green))
|
||||
}
|
||||
|
||||
for _, row := range rows {
|
||||
for _, field := range row {
|
||||
t.AddField(field, tableprinter.WithTruncate(nil))
|
||||
}
|
||||
t.EndRow()
|
||||
}
|
||||
|
||||
if err := t.Render(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
61
pkg/cmd/attestation/test/data/sigstore-js-2.1.0-bundle.json
Normal file
61
pkg/cmd/attestation/test/data/sigstore-js-2.1.0-bundle.json
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
{
|
||||
"mediaType": "application/vnd.dev.sigstore.bundle+json;version=0.1",
|
||||
"verificationMaterial": {
|
||||
"x509CertificateChain": {
|
||||
"certificates": [
|
||||
{
|
||||
"rawBytes": "MIIGtDCCBjugAwIBAgIUCJLipSt09KLFc0JYfuDrSan//LswCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjMwODI5MTU0MDIzWhcNMjMwODI5MTU1MDIzWjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEPfm6LPXQeJTC89UOqiNWmnZYGmX4T3iLZGi0EV4bfOoM86Hza94XqyuwxAoWpCPecFCEbAe8l2dg/er3O9LEFqOCBVowggVWMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUeqpCXHr3pcUaL3EFKR+KsmuKQqowHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wYwYDVR0RAQH/BFkwV4ZVaHR0cHM6Ly9naXRodWIuY29tL3NpZ3N0b3JlL3NpZ3N0b3JlLWpzLy5naXRodWIvd29ya2Zsb3dzL3JlbGVhc2UueW1sQHJlZnMvaGVhZHMvbWFpbjA5BgorBgEEAYO/MAEBBCtodHRwczovL3Rva2VuLmFjdGlvbnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tMBIGCisGAQQBg78wAQIEBHB1c2gwNgYKKwYBBAGDvzABAwQoMjZkMTY1MTMzODZmZmFhNzkwYjFjMzJmOTI3NTQ0ZjEzMjJlNDE5NDAVBgorBgEEAYO/MAEEBAdSZWxlYXNlMCIGCisGAQQBg78wAQUEFHNpZ3N0b3JlL3NpZ3N0b3JlLWpzMB0GCisGAQQBg78wAQYED3JlZnMvaGVhZHMvbWFpbjA7BgorBgEEAYO/MAEIBC0MK2h0dHBzOi8vdG9rZW4uYWN0aW9ucy5naXRodWJ1c2VyY29udGVudC5jb20wZQYKKwYBBAGDvzABCQRXDFVodHRwczovL2dpdGh1Yi5jb20vc2lnc3RvcmUvc2lnc3RvcmUtanMvLmdpdGh1Yi93b3JrZmxvd3MvcmVsZWFzZS55bWxAcmVmcy9oZWFkcy9tYWluMDgGCisGAQQBg78wAQoEKgwoMjZkMTY1MTMzODZmZmFhNzkwYjFjMzJmOTI3NTQ0ZjEzMjJlNDE5NDAdBgorBgEEAYO/MAELBA8MDWdpdGh1Yi1ob3N0ZWQwNwYKKwYBBAGDvzABDAQpDCdodHRwczovL2dpdGh1Yi5jb20vc2lnc3RvcmUvc2lnc3RvcmUtanMwOAYKKwYBBAGDvzABDQQqDCgyNmQxNjUxMzM4NmZmYWE3OTBiMWMzMmY5Mjc1NDRmMTMyMmU0MTk0MB8GCisGAQQBg78wAQ4EEQwPcmVmcy9oZWFkcy9tYWluMBkGCisGAQQBg78wAQ8ECwwJNDk1NTc0NTU1MCsGCisGAQQBg78wARAEHQwbaHR0cHM6Ly9naXRodWIuY29tL3NpZ3N0b3JlMBgGCisGAQQBg78wAREECgwINzEwOTYzNTMwZQYKKwYBBAGDvzABEgRXDFVodHRwczovL2dpdGh1Yi5jb20vc2lnc3RvcmUvc2lnc3RvcmUtanMvLmdpdGh1Yi93b3JrZmxvd3MvcmVsZWFzZS55bWxAcmVmcy9oZWFkcy9tYWluMDgGCisGAQQBg78wARMEKgwoMjZkMTY1MTMzODZmZmFhNzkwYjFjMzJmOTI3NTQ0ZjEzMjJlNDE5NDAUBgorBgEEAYO/MAEUBAYMBHB1c2gwWgYKKwYBBAGDvzABFQRMDEpodHRwczovL2dpdGh1Yi5jb20vc2lnc3RvcmUvc2lnc3RvcmUtanMvYWN0aW9ucy9ydW5zLzYwMTQ0ODg2NjYvYXR0ZW1wdHMvMTAWBgorBgEEAYO/MAEWBAgMBnB1YmxpYzCBigYKKwYBBAHWeQIEAgR8BHoAeAB2AN09MGrGxxEyYxkeHJlnNwKiSl643jyt/4eKcoAvKe6OAAABikHz+vwAAAQDAEcwRQIgZEo8c0eCZHEh4uzzJzFz9T+EfSTNTtB2FIH18vXpkOsCIQDE1MTti9RoDnRO3SET1Zkad6FoTx/k6ztQcwIDPmnRxTAKBggqhkjOPQQDAwNnADBkAjBB06fmNXx6ToaClFg2kOxnfLGgrvoR3F5GjDtvDBB8m9SWQNzL211jYmS/g+YbbyUCMC+ad6jIK+efe4XOIlhLcWxeZbBtMjKSrPxmm4jR3BFQQOBR+8r27CioyhxoSqvLuw=="
|
||||
}
|
||||
]
|
||||
},
|
||||
"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": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
BIN
pkg/cmd/attestation/test/data/sigstore-js-2.1.0.tgz
Normal file
BIN
pkg/cmd/attestation/test/data/sigstore-js-2.1.0.tgz
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
"mediaType": "application/vnd.dev.sigstore.bundle+json;version=0.1",
|
||||
"verificationMaterial": {
|
||||
"tlogEntries": [
|
||||
{
|
||||
"logIndex": "6755099",
|
||||
"logId": {
|
||||
"keyId": "wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="
|
||||
},
|
||||
"kindVersion": {
|
||||
"kind": "intoto",
|
||||
"version": "0.0.2"
|
||||
},
|
||||
"integratedTime": "1667953968",
|
||||
"inclusionPromise": {
|
||||
"signedEntryTimestamp": "MEUCIACiV07L0UdN6++1ljz5fbK9L5w3epXGvb/tRRmg+LlOAiEA+ksZQyovogZd2Y2YH0A9iXle3Cee90F/TU2h9DcdkbA="
|
||||
},
|
||||
"canonicalizedBody": "eyJhcGlWZXJzaW9uIjoiMC4wLjIiLCJraW5kIjoiaW50b3RvIiwic3BlYyI6eyJjb250ZW50Ijp7ImVudmVsb3BlIjp7InBheWxvYWRUeXBlIjoidGV4dC9wbGFpbiIsInNpZ25hdHVyZXMiOlt7InB1YmxpY0tleSI6IkxTMHRMUzFDUlVkSlRpQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENrMUpTVU52YWtORFFXbGxaMEYzU1VKQlowbFZXbXMyUWpGRE56UjZURXBMTDBOblMwMWhOV3AyVmtaWk9IZE5kME5uV1VsTGIxcEplbW93UlVGM1RYY0tUbnBGVmsxQ1RVZEJNVlZGUTJoTlRXTXliRzVqTTFKMlkyMVZkVnBIVmpKTlVqUjNTRUZaUkZaUlVVUkZlRlo2WVZka2VtUkhPWGxhVXpGd1ltNVNiQXBqYlRGc1drZHNhR1JIVlhkSWFHTk9UV3BKZUUxVVFUVk5SRUY2VFdwUk0xZG9ZMDVOYWtsNFRWUkJOVTFFUVRCTmFsRXpWMnBCUVUxR2EzZEZkMWxJQ2t0dldrbDZhakJEUVZGWlNVdHZXa2w2YWpCRVFWRmpSRkZuUVVWb1psWkVla0ZoZWxjM1RERjVPREE1TVdObFNtWmFUbU16VmxWTll6VlpRVEpNWTNvS1pqUjNhRXBQVjJ4RGNuZExXVE5IUXpKTlVpdEVWbEp6YVZGck1HRTBWRUpWYkhWT2F6Sk5hM1pOZHpodE9VdEllalpQUTBGVldYZG5aMFpEVFVFMFJ3cEJNVlZrUkhkRlFpOTNVVVZCZDBsSVowUkJWRUpuVGxaSVUxVkZSRVJCUzBKblozSkNaMFZHUWxGalJFRjZRV1JDWjA1V1NGRTBSVVpuVVZWd2FFSjFDa2xLTjBGbWVucGlMMVpTUmt0WWNIRm5aMUUzUW1kQmQwaDNXVVJXVWpCcVFrSm5kMFp2UVZVek9WQndlakZaYTBWYVlqVnhUbXB3UzBaWGFYaHBORmtLV2tRNGQwaDNXVVJXVWpCU1FWRklMMEpDVlhkRk5FVlNXVzVLY0ZsWE5VRmFSMVp2V1ZjeGJHTnBOV3BpTWpCM1RFRlpTMHQzV1VKQ1FVZEVkbnBCUWdwQlVWRmxZVWhTTUdOSVRUWk1lVGx1WVZoU2IyUlhTWFZaTWpsMFRESjRkbG95YkhWTU1qbG9aRmhTYjAxSlIweENaMjl5UW1kRlJVRmtXalZCWjFGRENrSklNRVZsZDBJMVFVaGpRVE5VTUhkaGMySklSVlJLYWtkU05HTnRWMk16UVhGS1MxaHlhbVZRU3pNdmFEUndlV2RET0hBM2J6UkJRVUZIUlZkak0wUUthMmRCUVVKQlRVRlRSRUpIUVdsRlFTdFliemxHZGl0dVpHTlpNazlzTldJeVRUaEZTSGhUTTBob2IwSmlTalpTUWpKVVkzWXZURTAwZVZGRFNWRkVaQXBzY1c1U2JFbzVWWEJWTkdwT2VXaGhRbE5FWjBSMGVEY3JPRTVSTVVkdU56RTBjMlJZVVdGdlJtcEJTMEpuWjNGb2EycFBVRkZSUkVGM1RuQkJSRUp0Q2tGcVJVRnViWGRXWlZGMVJYTnhaMVV4U0hKS1RrSlZaSEIxTlhVMlozZFhSSFExVGxWR09YcFphWFI1YUZWTlQwZFdTMWRRUmxKek1YcFJlVEkyYldjS1JtZ3hjMEZxUlVGM1ZqRlRabGRtTnk5QlNUWllRVzVZYUdNMFZrSllRaTlKVFhkVlFuSmxjbEJ4WVVwS2NWRTNSblF4ZGtKQ2JpOXFSRkpaYVhOelJ3cDFjVWx5V1VGWFN3b3RMUzB0TFVWT1JDQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENnPT0iLCJzaWciOiJUVVZWUTBsQ1ZUSldiMFUxV0c5VE9EQkZWamhxVHpCU2RsRmhWRzFsUWtWTFIwVjBWVUZKYTJKa1FsUnNXV3QxUVdsRlFXNDJkazl6VkRsNlJWcHpSemhpV1ROdlJUVTBXVWhsTHpWQmNFTXZXVFZwVFZCWWRqaGhjREIyUlZFOSJ9XX0sImhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI5ODQ2YjRlMDU0ZWYwYzVlZWIwNzA1NDQ0N2I2OWQ2ODgxZWRkNmIwMTZlOWU4Nzg0M2YyMzVhOWMyMDFkMzAzIn0sInBheWxvYWRIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiNjhlNjU2YjI1MWU2N2U4MzU4YmVmODQ4M2FiMGQ1MWM2NjE5ZjNlN2ExYTlmMGU3NTgzOGQ0MWZmMzY4ZjcyOCJ9fX19"
|
||||
}
|
||||
],
|
||||
"timestampVerificationData": {
|
||||
"rfc3161Timestamps": []
|
||||
},
|
||||
"x509CertificateChain": {
|
||||
"certificates": [
|
||||
{
|
||||
"rawBytes": "MIICoDCCAiagAwIBAgIUSkFzCwp0c8PRty4So7bizLRExZswCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjIxMTA5MDAyOTI1WhcNMjIxMTA5MDAzOTI1WjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEj/l/5Pi+TWUkamLY1UH9yq0XgeH818M38IQm6rYdeT0JLLRozmzqGGUjoAFw1G69fBTAP0ae22V93BFT2JGYjqOCAUUwggFBMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUZzrPkPRD8aCDHvxscd7v6T5fOZMwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wHwYDVR0RAQH/BBUwE4ERYnJpYW5AZGVoYW1lci5jb20wLAYKKwYBBAGDvzABAQQeaHR0cHM6Ly9naXRodWIuY29tL2xvZ2luL29hdXRoMIGKBgorBgEEAdZ5AgQCBHwEegB4AHYA3T0wasbHETJjGR4cmWc3AqJKXrjePK3/h4pygC8p7o4AAAGEWcqsQgAABAMARzBFAiEA0XEwx48N/TZneFxEV7RwbVIhROPOMJYo1XEoN4sd98wCIGlL4RnI8JVHdYbzbZLHUIr7+5I1Dl//zUJBh4A3YPnzMAoGCCqGSM49BAMDA2gAMGUCMQDyFvX31sp+olif92nr/w52KLmtPoWVHxtGeaxuNAtQ7DFdtSDvXGenUT+fqALnAikCMD/hbwNuQl9kr2nlZ96GlT3eMCl78o0D2QKjx2Do1JC6lqG2hDM61FG6Znx7T5VWfQ=="
|
||||
},
|
||||
{
|
||||
"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": "MEYCIQDBfvwEED9oUrvTjZ7cJrZkE073IqG8V8r5I1wMY3YAJwIhAJ+UXICfI5rs7Jl2oFEAK8KG+asRWQA+ZkRA1jW2lUYO",
|
||||
"keyid": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -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": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
16
pkg/cmd/attestation/test/output.go
Normal file
16
pkg/cmd/attestation/test/output.go
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
package test
|
||||
|
||||
import "os"
|
||||
|
||||
func SuppressAndRestoreOutput() func() {
|
||||
null, _ := os.Open(os.DevNull)
|
||||
stdOut := os.Stdout
|
||||
stdErr := os.Stderr
|
||||
os.Stdout = null
|
||||
os.Stderr = null
|
||||
return func() {
|
||||
defer null.Close()
|
||||
os.Stdout = stdOut
|
||||
os.Stderr = stdErr
|
||||
}
|
||||
}
|
||||
112
pkg/cmd/attestation/verification/attestation.go
Normal file
112
pkg/cmd/attestation/verification/attestation.go
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
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: %w", 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: %w", 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: %w", 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: %w", 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) {
|
||||
// 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")
|
||||
}
|
||||
49
pkg/cmd/attestation/verification/attestation_test.go
Normal file
49
pkg/cmd/attestation/verification/attestation_test.go
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
package verification
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestLoadBundlesFromJSONLinesFile(t *testing.T) {
|
||||
path := "../test/data/sigstore-js-2.1.0_with_2_bundles.jsonl"
|
||||
attestations, err := loadBundlesFromJSONLinesFile(path)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, attestations, 2)
|
||||
}
|
||||
|
||||
func TestLoadBundleFromJSONFile(t *testing.T) {
|
||||
path := "../test/data/sigstore-js-2.1.0-bundle.json"
|
||||
attestations, err := loadBundleFromJSONFile(path)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.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)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.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)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.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)
|
||||
|
||||
assert.ErrorIs(t, err, ErrLocalAttestations)
|
||||
assert.Nil(t, attestations)
|
||||
})
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +1,20 @@
|
|||
package verify
|
||||
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(artifactDigest, digestAlgorithm string) (verify.ArtifactPolicyOption, error) {
|
||||
func BuildDigestPolicyOption(a artifact.DigestedArtifact) (verify.ArtifactPolicyOption, error) {
|
||||
// sigstore-go expects the artifact digest to be decoded from hex
|
||||
decoded, err := hex.DecodeString(artifactDigest)
|
||||
decoded, err := hex.DecodeString(a.Digest())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return verify.WithArtifactDigest(digestAlgorithm, decoded), nil
|
||||
return verify.WithArtifactDigest(a.Algorithm(), decoded), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,27 +0,0 @@
|
|||
package verify
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/cli/cli/v2/pkg/cmd/attestation/artifact"
|
||||
"github.com/cli/cli/v2/pkg/cmd/attestation/github"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestBuildPolicy(t *testing.T) {
|
||||
opts := &VerifyOpts{
|
||||
ArtifactPath: "../../test/data/public-good/sigstore-js-2.1.0.tgz",
|
||||
BundlePath: "../../test/data/public-good/sigstore-js-2.1.0-bundle.json",
|
||||
GitHubClient: github.NewTestClient(),
|
||||
OIDCIssuer: GitHubOIDCIssuer,
|
||||
SAN: "fake san",
|
||||
Owner: "github",
|
||||
}
|
||||
|
||||
artifact, err := artifact.NewDigestedArtifact(opts.OCIClient, opts.ArtifactPath, opts.DigestAlgorithm)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = buildVerifyPolicy(opts, artifact.Digest())
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
package verify
|
||||
package verification
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cli/cli/v2/pkg/cmd/attestation/github"
|
||||
"github.com/cli/cli/v2/pkg/cmd/attestation/api"
|
||||
"github.com/cli/cli/v2/pkg/cmd/attestation/logger"
|
||||
|
||||
"github.com/sigstore/sigstore-go/pkg/bundle"
|
||||
"github.com/sigstore/sigstore-go/pkg/root"
|
||||
|
|
@ -16,9 +17,20 @@ const (
|
|||
GitHubIssuerOrg = "GitHub, Inc."
|
||||
)
|
||||
|
||||
// AttestationProcessingResult captures processing a given attestation's signature verification and policy evaluation
|
||||
type AttestationProcessingResult struct {
|
||||
Attestation *api.Attestation
|
||||
VerificationResult *verify.VerificationResult
|
||||
}
|
||||
|
||||
type SigstoreResults struct {
|
||||
VerifyResults []*AttestationProcessingResult
|
||||
Error error
|
||||
}
|
||||
|
||||
type SigstoreConfig struct {
|
||||
CustomTrustedRoot string
|
||||
Logger *output.Logger
|
||||
Logger *logger.Logger
|
||||
NoPublicGood bool
|
||||
}
|
||||
|
||||
|
|
@ -28,7 +40,7 @@ type SigstoreVerifier struct {
|
|||
customVerifier *verify.SignedEntityVerifier
|
||||
policy verify.PolicyBuilder
|
||||
onlyVerifyWithGithub bool
|
||||
Logger *output.Logger
|
||||
Logger *logger.Logger
|
||||
}
|
||||
|
||||
// NewSigstoreVerifier creates a new SigstoreVerifier struct
|
||||
|
|
@ -91,7 +103,7 @@ func (v *SigstoreVerifier) chooseVerifier(b *bundle.ProtobufBundle) (*verify.Sig
|
|||
return nil, "", fmt.Errorf("leaf certificate issuer is not recognized")
|
||||
}
|
||||
|
||||
func (v *SigstoreVerifier) Verify(attestations []*github.Attestation) *SigstoreResults {
|
||||
func (v *SigstoreVerifier) Verify(attestations []*api.Attestation) *SigstoreResults {
|
||||
// initialize the processing results before attempting to verify
|
||||
// with multiple verifiers
|
||||
results := make([]*AttestationProcessingResult, len(attestations))
|
||||
|
|
|
|||
|
|
@ -1,53 +1,49 @@
|
|||
package verify
|
||||
package verification
|
||||
|
||||
import (
|
||||
"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/github"
|
||||
"github.com/cli/cli/v2/pkg/cmd/attestation/logger"
|
||||
"github.com/cli/cli/v2/pkg/cmd/attestation/test"
|
||||
|
||||
"github.com/sigstore/sigstore-go/pkg/verify"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var logger = output.NewDefaultLogger()
|
||||
|
||||
var publicGoodOpts = Options{
|
||||
ArtifactPath: test.NormalizeRelativePath("../../test/data/public-good/sigstore-js-2.1.0.tgz"),
|
||||
BundlePath: test.NormalizeRelativePath("../../test/data/public-good/sigstore-js-2.1.0-bundle.json"),
|
||||
DigestAlgorithm: "sha512",
|
||||
GitHubClient: github.NewTestClient(),
|
||||
Logger: logger,
|
||||
OIDCIssuer: GitHubOIDCIssuer,
|
||||
Owner: "sigstore",
|
||||
Limit: 30,
|
||||
OCIClient: oci.NewMockClient(),
|
||||
SANRegex: "^https://github.com/sigstore/",
|
||||
}
|
||||
var (
|
||||
artifactPath = test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0.tgz")
|
||||
bundlePath = test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0-bundle.json")
|
||||
ociClient = oci.NewMockClient()
|
||||
)
|
||||
|
||||
func TestVerify_PublicGoodSuccess(t *testing.T) {
|
||||
t.Skip()
|
||||
res := test.SuppressAndRestoreOutput()
|
||||
defer res()
|
||||
|
||||
artifact, err := artifact.NewDigestedArtifact(publicGoodOpts.OCIClient, publicGoodOpts.ArtifactPath, publicGoodOpts.DigestAlgorithm)
|
||||
artifact, err := artifact.NewDigestedArtifact(ociClient, artifactPath, "sha512")
|
||||
require.NoError(t, err)
|
||||
|
||||
attestations, err := getAttestations(&publicGoodOpts, artifact.Digest())
|
||||
require.NoError(t, err)
|
||||
c := FetchAttestationsConfig{
|
||||
APIClient: api.NewTestClient(),
|
||||
BundlePath: test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0-bundle.json"),
|
||||
Digest: artifact.DigestWithAlg(),
|
||||
Owner: "sigstore",
|
||||
}
|
||||
|
||||
policy, err := buildVerifyPolicy(&publicGoodOpts, artifact.Digest())
|
||||
attestations, err := GetAttestations(c)
|
||||
require.NoError(t, err)
|
||||
|
||||
config := SigstoreConfig{
|
||||
CustomTrustedRoot: publicGoodOpts.CustomTrustedRoot,
|
||||
Logger: publicGoodOpts.Logger,
|
||||
NoPublicGood: publicGoodOpts.NoPublicGood,
|
||||
Logger: logger.NewDefaultLogger(),
|
||||
NoPublicGood: false,
|
||||
}
|
||||
|
||||
v, err := NewSigstoreVerifier(config, policy)
|
||||
v, err := NewSigstoreVerifier(config, verify.PolicyBuilder{})
|
||||
require.NoError(t, err)
|
||||
|
||||
resp := v.Verify(attestations)
|
||||
|
|
@ -62,32 +58,27 @@ func TestVerify_PublicGoodSuccess(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestVerify_PublicGoodFail_WithNoPublicGoodFlag(t *testing.T) {
|
||||
t.Skip()
|
||||
res := test.SuppressAndRestoreOutput()
|
||||
defer res()
|
||||
|
||||
opts := publicGoodOpts
|
||||
opts.NoPublicGood = true
|
||||
|
||||
err := opts.AreFlagsValid()
|
||||
artifact, err := artifact.NewDigestedArtifact(ociClient, artifactPath, "sha512")
|
||||
require.NoError(t, err)
|
||||
|
||||
artifact, err := artifact.NewDigestedArtifact(opts.OCIClient, opts.ArtifactPath, opts.DigestAlgorithm)
|
||||
require.NoError(t, err)
|
||||
c := FetchAttestationsConfig{
|
||||
BundlePath: bundlePath,
|
||||
Digest: artifact.Digest(),
|
||||
Owner: "sigstore",
|
||||
}
|
||||
|
||||
attestations, err := getAttestations(&opts, artifact.Digest())
|
||||
require.NoError(t, err)
|
||||
|
||||
policy, err := buildVerifyPolicy(&publicGoodOpts, artifact.Digest())
|
||||
attestations, err := GetAttestations(c)
|
||||
require.NoError(t, err)
|
||||
|
||||
config := SigstoreConfig{
|
||||
CustomTrustedRoot: opts.CustomTrustedRoot,
|
||||
Logger: opts.Logger,
|
||||
NoPublicGood: opts.NoPublicGood,
|
||||
Logger: logger.NewDefaultLogger(),
|
||||
NoPublicGood: true,
|
||||
}
|
||||
|
||||
v, err := NewSigstoreVerifier(config, policy)
|
||||
v, err := NewSigstoreVerifier(config, verify.PolicyBuilder{})
|
||||
require.NoError(t, err)
|
||||
|
||||
resp := v.Verify(attestations)
|
||||
|
|
@ -99,28 +90,24 @@ func TestVerify_Failure(t *testing.T) {
|
|||
res := test.SuppressAndRestoreOutput()
|
||||
defer res()
|
||||
|
||||
opts := publicGoodOpts
|
||||
opts.DigestAlgorithm = "sha256"
|
||||
|
||||
err := opts.AreFlagsValid()
|
||||
artifact, err := artifact.NewDigestedArtifact(ociClient, artifactPath, "sha512")
|
||||
require.NoError(t, err)
|
||||
|
||||
artifact, err := artifact.NewDigestedArtifact(opts.OCIClient, opts.ArtifactPath, opts.DigestAlgorithm)
|
||||
require.NoError(t, err)
|
||||
c := FetchAttestationsConfig{
|
||||
BundlePath: bundlePath,
|
||||
Digest: artifact.Digest(),
|
||||
Owner: "sigstore",
|
||||
}
|
||||
|
||||
attestations, err := getAttestations(&opts, artifact.Digest())
|
||||
require.NoError(t, err)
|
||||
|
||||
policy, err := buildVerifyPolicy(&opts, artifact.Digest())
|
||||
attestations, err := GetAttestations(c)
|
||||
require.NoError(t, err)
|
||||
|
||||
config := SigstoreConfig{
|
||||
CustomTrustedRoot: opts.CustomTrustedRoot,
|
||||
Logger: opts.Logger,
|
||||
NoPublicGood: opts.NoPublicGood,
|
||||
Logger: logger.NewDefaultLogger(),
|
||||
NoPublicGood: true,
|
||||
}
|
||||
|
||||
v, err := NewSigstoreVerifier(config, policy)
|
||||
v, err := NewSigstoreVerifier(config, verify.PolicyBuilder{})
|
||||
require.NoError(t, err)
|
||||
|
||||
resp := v.Verify(attestations)
|
||||
|
|
|
|||
55
pkg/cmd/attestation/verification/tuf.go
Normal file
55
pkg/cmd/attestation/verification/tuf.go
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
package verification
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/sigstore/sigstore-go/pkg/tuf"
|
||||
)
|
||||
|
||||
//go:embed embed
|
||||
var embeddedRepos embed.FS
|
||||
|
||||
const GitHubTUFMirror = "https://tuf-repo.github.com"
|
||||
|
||||
// readEmbeddedRoot reads the embedded trust anchor for the given URL
|
||||
func readEmbeddedRoot(url string) ([]byte, error) {
|
||||
// the embed file system always uses forward slashes, even on Windows
|
||||
p := fmt.Sprintf("embed/%s/root.json", tuf.URLToPath(url))
|
||||
|
||||
b, err := embeddedRepos.ReadFile(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
return opts
|
||||
}
|
||||
|
||||
func GitHubTUFOptions() (*tuf.Options, error) {
|
||||
opts := DefaultOptionsWithCacheSetting()
|
||||
|
||||
// replace root and mirror url
|
||||
root, err := readEmbeddedRoot(GitHubTUFMirror)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opts.Root = root
|
||||
opts.RepositoryBaseURL = GitHubTUFMirror
|
||||
|
||||
return opts, nil
|
||||
}
|
||||
18
pkg/cmd/attestation/verification/tuf_test.go
Normal file
18
pkg/cmd/attestation/verification/tuf_test.go
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
package verification
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGitHubTUFOptions(t *testing.T) {
|
||||
os.Setenv("CODESPACES", "true")
|
||||
opts, err := GitHubTUFOptions()
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, GitHubTUFMirror, opts.RepositoryBaseURL)
|
||||
assert.NotNil(t, opts.Root)
|
||||
assert.True(t, opts.DisableLocalCache)
|
||||
}
|
||||
|
|
@ -7,15 +7,8 @@ import (
|
|||
|
||||
"github.com/cli/cli/v2/pkg/cmd/attestation/artifact/digest"
|
||||
"github.com/cli/cli/v2/pkg/cmd/attestation/artifact/oci"
|
||||
"github.com/cli/cli/v2/pkg/cmd/attestation/github"
|
||||
)
|
||||
|
||||
const (
|
||||
// OfflineMode is used when the user provides a bundle path
|
||||
OfflineMode = "offline"
|
||||
// OnlineMode is used when the user does not provide a bundle path
|
||||
// An owner or repo scope is used to fetch attestations from GitHub
|
||||
OnlineMode = "online"
|
||||
"github.com/cli/cli/v2/pkg/cmd/attestation/api"
|
||||
"github.com/cli/cli/v2/pkg/cmd/attestation/logger"
|
||||
)
|
||||
|
||||
// Options captures the options for the verify command
|
||||
|
|
@ -34,35 +27,12 @@ type Options struct {
|
|||
SAN string
|
||||
SANRegex string
|
||||
Verbose bool
|
||||
GitHubClient github.Client
|
||||
Logger *output.Logger
|
||||
APIClient api.Client
|
||||
Logger *logger.Logger
|
||||
Limit int
|
||||
OCIClient oci.Client
|
||||
}
|
||||
|
||||
// Mode returns a string indicating either online or offline mode
|
||||
func (opts *Options) Mode() string {
|
||||
if opts.BundlePath == "" {
|
||||
return OnlineMode
|
||||
}
|
||||
return OfflineMode
|
||||
}
|
||||
|
||||
// ConfigureGitHubClient configures a live GitHub client if the tool
|
||||
// is running in online mode
|
||||
func (opts *Options) ConfigureGitHubClient(version, date string) error {
|
||||
if opts.Mode() == OfflineMode {
|
||||
return nil
|
||||
}
|
||||
|
||||
client, err := github.NewLiveClient(version, date)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
opts.GitHubClient = client
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConfigureOCIClient configures an OCI client
|
||||
func (opts *Options) ConfigureOCIClient() {
|
||||
opts.OCIClient = oci.NewLiveClient()
|
||||
|
|
@ -71,7 +41,7 @@ func (opts *Options) ConfigureOCIClient() {
|
|||
// ConfigureLogger configures a logger using configuration provided
|
||||
// through the options
|
||||
func (opts *Options) ConfigureLogger() {
|
||||
opts.Logger = output.NewLogger(opts.Quiet, opts.Verbose)
|
||||
opts.Logger = logger.NewLogger(opts.Quiet, opts.Verbose)
|
||||
}
|
||||
|
||||
// Clean cleans the file path option values
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
publicGoodArtifactPath = test.NormalizeRelativePath("../../test/data/public-good/sigstore-js-2.1.0.tgz")
|
||||
publicGoodBundlePath = test.NormalizeRelativePath("../../test/data/public-good/sigstore-js-2.1.0-bundle.json")
|
||||
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) {
|
||||
|
|
@ -195,35 +195,3 @@ func TestClean(t *testing.T) {
|
|||
opts.Clean()
|
||||
assert.Equal(t, validBundlePath, opts.BundlePath)
|
||||
}
|
||||
|
||||
func TestMode(t *testing.T) {
|
||||
t.Run("run in offline mode when bundle is provided", func(t *testing.T) {
|
||||
opts := Options{
|
||||
ArtifactPath: publicGoodArtifactPath,
|
||||
BundlePath: publicGoodBundlePath,
|
||||
}
|
||||
|
||||
assert.Equal(t, OfflineMode, opts.Mode())
|
||||
})
|
||||
|
||||
t.Run("run in offline mode when bundle and repo are provided", func(t *testing.T) {
|
||||
opts := &Options{
|
||||
ArtifactPath: publicGoodArtifactPath,
|
||||
BundlePath: publicGoodBundlePath,
|
||||
DigestAlgorithm: "sha512",
|
||||
Repo: "sigstore/sigstore-js",
|
||||
}
|
||||
|
||||
assert.Equal(t, OfflineMode, opts.Mode())
|
||||
})
|
||||
|
||||
t.Run("run in online mode when repo are provided", func(t *testing.T) {
|
||||
opts := &Options{
|
||||
ArtifactPath: publicGoodArtifactPath,
|
||||
DigestAlgorithm: "sha512",
|
||||
Repo: "sigstore/sigstore-js",
|
||||
}
|
||||
|
||||
assert.Equal(t, OnlineMode, opts.Mode())
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"github.com/sigstore/sigstore-go/pkg/verify"
|
||||
"github.com/sigstore/sigstore-go/pkg/fulcio/certificate"
|
||||
|
||||
"github.com/cli/cli/v2/pkg/cmd/attestation/artifact"
|
||||
"github.com/cli/cli/v2/pkg/cmd/attestation/verification"
|
||||
)
|
||||
|
||||
|
|
@ -16,19 +17,20 @@ const (
|
|||
GitHubRunner = "github-hosted"
|
||||
)
|
||||
|
||||
func buildSANMatcher(opts *Options) (verify.SubjectAlternativeNameMatcher, error) {
|
||||
if opts.SAN != "" || opts.SANRegex != "" {
|
||||
sanMatcher, err := verify.NewSANMatcher(opts.SAN, "", opts.SANRegex)
|
||||
if err != nil {
|
||||
return verify.SubjectAlternativeNameMatcher{}, err
|
||||
}
|
||||
return sanMatcher, nil
|
||||
func buildSANMatcher(san, sanRegex string) (verify.SubjectAlternativeNameMatcher, error) {
|
||||
if san == "" && sanRegex == "" {
|
||||
return verify.SubjectAlternativeNameMatcher{}, nil
|
||||
}
|
||||
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)
|
||||
sanMatcher, err := buildSANMatcher(opts.SAN, opts.SANRegex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -67,8 +69,8 @@ func buildVerifyCertIdOption(opts *Options) (verify.PolicyOption, error) {
|
|||
return withAnyRunner, nil
|
||||
}
|
||||
|
||||
func buildVerifyPolicy(opts *Options, artifactDigest string) (verify.PolicyBuilder, error) {
|
||||
artifactDigestPolicyOption, err := verification.BuildDigestPolicyOption(artifactDigest, opts.DigestAlgorithm)
|
||||
func buildVerifyPolicy(opts *Options, a artifact.DigestedArtifact) (verify.PolicyBuilder, error) {
|
||||
artifactDigestPolicyOption, err := verification.BuildDigestPolicyOption(a)
|
||||
if err != nil {
|
||||
return verify.PolicyBuilder{}, err
|
||||
}
|
||||
|
|
|
|||
29
pkg/cmd/attestation/verify/policy_test.go
Normal file
29
pkg/cmd/attestation/verify/policy_test.go
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
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/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestBuildPolicy(t *testing.T) {
|
||||
ociClient := oci.NewMockClient()
|
||||
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,
|
||||
Owner: "sigstore",
|
||||
SANRegex: "^https://github.com/sigstore/",
|
||||
}
|
||||
|
||||
_, err = buildVerifyPolicy(opts, *artifact)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
|
@ -1,17 +1,25 @@
|
|||
package verify
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/pkg/cmd/attestation/github"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"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/logger"
|
||||
"github.com/cli/cli/v2/pkg/cmd/attestation/verification"
|
||||
|
||||
"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) *cobra.Command {
|
||||
opts := &verify.Options{}
|
||||
opts := &Options{}
|
||||
verifyCmd := &cobra.Command{
|
||||
Use: "verify <artifact-path-or-url>",
|
||||
Args: cobra.ExactArgs(1),
|
||||
|
|
@ -60,6 +68,8 @@ func NewVerifyCmd(f *cmdutil.Factory) *cobra.Command {
|
|||
// 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 {
|
||||
opts.APIClient = api.NewLiveClient()
|
||||
|
||||
// Create a logger for use throughout the verify command
|
||||
opts.ConfigureLogger()
|
||||
|
||||
|
|
@ -86,7 +96,7 @@ func NewVerifyCmd(f *cmdutil.Factory) *cobra.Command {
|
|||
// when RunE is used, the command usage will be printed
|
||||
// We only want to print the error, not usage
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if err := runVerify(opts); err != nil {
|
||||
if err := RunVerify(opts); err != nil {
|
||||
opts.Logger.Println(opts.Logger.ColorScheme.Redf("Failed to verify the artifact: %s", err.Error()))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
|
@ -99,24 +109,25 @@ func NewVerifyCmd(f *cmdutil.Factory) *cobra.Command {
|
|||
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 <owner>/<repo>")
|
||||
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().BoolVarP(&opts.JsonResult, "json-result", "j", false, "Output verification result as JSON lines")
|
||||
verifyCmd.Flags().BoolVarP(&opts.Quiet, "quiet", "q", false, "If set to true, the CLI will not print any diagnostic logging.")
|
||||
verifyCmd.Flags().BoolVarP(&opts.Verbose, "verbose", "v", false, "If set to true, the CLI will print verbose diagnostic logging.")
|
||||
verifyCmd.MarkFlagsMutuallyExclusive("quiet", "verbose")
|
||||
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", github.DefaultLimit, "Maximum number of attestations to fetch")
|
||||
verifyCmd.Flags().IntVarP(&opts.Limit, "limit", "L", api.DefaultLimit, "Maximum number of attestations to fetch")
|
||||
// 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", "", verify.GitHubOIDCIssuer, "Issuer of the OIDC token")
|
||||
verifyCmd.Flags().StringVarP(&opts.OIDCIssuer, "cert-oidc-issuer", "", GitHubOIDCIssuer, "Issuer of the OIDC token")
|
||||
|
||||
return verifyCmd
|
||||
}
|
||||
|
||||
func runVerify(opts *Options) error {
|
||||
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)
|
||||
|
|
@ -124,26 +135,33 @@ func runVerify(opts *Options) error {
|
|||
|
||||
opts.Logger.Printf("Verifying attestations for the artifact found at %s\n", artifact.URL)
|
||||
|
||||
attestations, err := getAttestations(opts, artifact.DigestWithAlg())
|
||||
c := verification.FetchAttestationsConfig{
|
||||
APIClient: opts.APIClient,
|
||||
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, github.ErrNoAttestations{}); ok {
|
||||
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.Digest())
|
||||
policy, err := buildVerifyPolicy(opts, *artifact)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to build policy: %w", err)
|
||||
}
|
||||
|
||||
config := SigstoreConfig{
|
||||
config := verification.SigstoreConfig{
|
||||
CustomTrustedRoot: opts.CustomTrustedRoot,
|
||||
Logger: opts.Logger,
|
||||
NoPublicGood: opts.NoPublicGood,
|
||||
}
|
||||
|
||||
sv, err := NewSigstoreVerifier(config, policy)
|
||||
sv, err := verification.NewSigstoreVerifier(config, policy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -189,7 +207,7 @@ func runVerify(opts *Options) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func verifySLSAPredicateType(logger *output.Logger, apr []*AttestationProcessingResult) error {
|
||||
func verifySLSAPredicateType(logger *logger.Logger, apr []*verification.AttestationProcessingResult) error {
|
||||
logger.VerbosePrintf("Evaluating attestations have valid SLSA predicate type...\n")
|
||||
|
||||
for _, result := range apr {
|
||||
|
|
|
|||
182
pkg/cmd/attestation/verify/verify_test.go
Normal file
182
pkg/cmd/attestation/verify/verify_test.go
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
package verify
|
||||
|
||||
import (
|
||||
"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/logger"
|
||||
"github.com/cli/cli/v2/pkg/cmd/attestation/test"
|
||||
"github.com/cli/cli/v2/pkg/cmd/attestation/verification"
|
||||
|
||||
"github.com/in-toto/in-toto-golang/in_toto"
|
||||
"github.com/sigstore/sigstore-go/pkg/verify"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const (
|
||||
SigstoreSanValue = "https://github.com/sigstore/sigstore-js/.github/workflows/release.yml@refs/heads/main"
|
||||
SigstoreSanRegex = "^https://github.com/sigstore/sigstore-js/"
|
||||
)
|
||||
|
||||
func TestRunVerify(t *testing.T) {
|
||||
res := test.SuppressAndRestoreOutput()
|
||||
defer res()
|
||||
|
||||
logger := logger.NewDefaultLogger()
|
||||
|
||||
publicGoodOpts := 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",
|
||||
APIClient: api.NewTestClient(),
|
||||
Logger: logger,
|
||||
OCIClient: oci.NewMockClient(),
|
||||
OIDCIssuer: GitHubOIDCIssuer,
|
||||
Owner: "sigstore",
|
||||
SANRegex: "^https://github.com/sigstore/",
|
||||
}
|
||||
|
||||
t.Run("with valid artifact and bundle", func(t *testing.T) {
|
||||
assert.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.NewReferenceFailClient()
|
||||
|
||||
err := RunVerify(&opts)
|
||||
assert.Error(t, err)
|
||||
assert.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"
|
||||
assert.Error(t, RunVerify(&opts))
|
||||
})
|
||||
|
||||
t.Run("with missing bundle path", func(t *testing.T) {
|
||||
opts := publicGoodOpts
|
||||
opts.BundlePath = "../test/data/non-existent-sigstoreBundle.json"
|
||||
assert.Error(t, RunVerify(&opts))
|
||||
})
|
||||
|
||||
t.Run("with invalid signature", func(t *testing.T) {
|
||||
opts := publicGoodOpts
|
||||
opts.BundlePath = "../test/data/sigstoreBundle-invalid-signature.json"
|
||||
|
||||
err := RunVerify(&opts)
|
||||
assert.Error(t, err)
|
||||
assert.ErrorContains(t, err, "at least one attestation failed to verify")
|
||||
assert.ErrorContains(t, err, "verifying with issuer \"sigstore.dev\"")
|
||||
})
|
||||
|
||||
t.Run("with owner", func(t *testing.T) {
|
||||
opts := publicGoodOpts
|
||||
opts.BundlePath = ""
|
||||
opts.Owner = "github"
|
||||
|
||||
assert.Nil(t, RunVerify(&opts))
|
||||
})
|
||||
|
||||
t.Run("with repo", func(t *testing.T) {
|
||||
opts := publicGoodOpts
|
||||
opts.BundlePath = ""
|
||||
opts.Repo = "github/example"
|
||||
|
||||
assert.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)
|
||||
assert.Error(t, err)
|
||||
assert.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)
|
||||
assert.Error(t, err)
|
||||
assert.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"
|
||||
assert.Error(t, RunVerify(&opts))
|
||||
})
|
||||
|
||||
t.Run("with SAN enforcement", func(t *testing.T) {
|
||||
opts := Options{
|
||||
ArtifactPath: "../test/data/sigstore-js-2.1.0.tgz",
|
||||
BundlePath: "../test/data/sigstore-js-2.1.0-bundle.json",
|
||||
APIClient: api.NewTestClient(),
|
||||
DigestAlgorithm: "sha512",
|
||||
Logger: logger,
|
||||
OIDCIssuer: GitHubOIDCIssuer,
|
||||
Owner: "sigstore",
|
||||
SAN: SigstoreSanValue,
|
||||
}
|
||||
assert.Nil(t, RunVerify(&opts))
|
||||
})
|
||||
|
||||
t.Run("with invalid SAN", func(t *testing.T) {
|
||||
opts := publicGoodOpts
|
||||
opts.SAN = "fake san"
|
||||
assert.Error(t, RunVerify(&opts))
|
||||
})
|
||||
|
||||
t.Run("with SAN regex enforcement", func(t *testing.T) {
|
||||
opts := publicGoodOpts
|
||||
opts.SANRegex = SigstoreSanRegex
|
||||
assert.Nil(t, RunVerify(&opts))
|
||||
})
|
||||
|
||||
t.Run("with invalid SAN regex", func(t *testing.T) {
|
||||
opts := publicGoodOpts
|
||||
opts.SANRegex = "^https://github.com/sigstore/not-real/"
|
||||
assert.Error(t, RunVerify(&opts))
|
||||
})
|
||||
|
||||
t.Run("with no matching OIDC issuer", func(t *testing.T) {
|
||||
opts := publicGoodOpts
|
||||
opts.OIDCIssuer = "some-other-issuer"
|
||||
|
||||
assert.Error(t, RunVerify(&opts))
|
||||
})
|
||||
|
||||
t.Run("with valid artifact and JSON lines file containing multiple Sigstore bundles", func(t *testing.T) {
|
||||
opts := publicGoodOpts
|
||||
opts.BundlePath = "../test/data/sigstore-js-2.1.0_with_2_bundles.jsonl"
|
||||
assert.Nil(t, RunVerify(&opts))
|
||||
})
|
||||
}
|
||||
|
||||
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(logger.NewDefaultLogger(), apr)
|
||||
assert.Error(t, err)
|
||||
assert.ErrorIs(t, err, ErrNoMatchingSLSAPredicate)
|
||||
}
|
||||
|
|
@ -125,7 +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.IOStreams, version, buildDate))
|
||||
cmd.AddCommand(attestationCmd.NewCmdAttestation(f))
|
||||
cmd.AddCommand(configCmd.NewCmdConfig(f))
|
||||
cmd.AddCommand(creditsCmd.NewCmdCredits(f, nil))
|
||||
cmd.AddCommand(gistCmd.NewCmdGist(f))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue