* add attestation cmd
Signed-off-by: Meredith Lancaster <malancas@github.com>
* add codeowners
Signed-off-by: Meredith Lancaster <malancas@github.com>
* update args passed to the attestation cmd
Signed-off-by: Meredith Lancaster <malancas@github.com>
* rename file
Signed-off-by: Meredith Lancaster <malancas@github.com>
* use gh-attestation branch for passing iostreams from the root
Signed-off-by: Meredith Lancaster <malancas@github.com>
* add package security team entry to codeowners
Signed-off-by: Meredith Lancaster <malancas@github.com>
* start moving over verify cmd and general verification code
Signed-off-by: Meredith Lancaster <malancas@github.com>
* clean up common and verify specific policy code
Signed-off-by: Meredith Lancaster <malancas@github.com>
* move artifact package over
Signed-off-by: Meredith Lancaster <malancas@github.com>
* start pulling in the github api client wrapper
Signed-off-by: Meredith Lancaster <malancas@github.com>
* fix imports
Signed-off-by: Meredith Lancaster <malancas@github.com>
* add logger and test packages
Signed-off-by: Meredith Lancaster <malancas@github.com>
* add additional packages to support verify command
Signed-off-by: Meredith Lancaster <malancas@github.com>
* fix mock api client
Signed-off-by: Meredith Lancaster <malancas@github.com>
* clean up mock api client
Signed-off-by: Meredith Lancaster <malancas@github.com>
* include missing fields
Signed-off-by: Meredith Lancaster <malancas@github.com>
* use correct owner
Signed-off-by: Meredith Lancaster <malancas@github.com>
* add more mock api client options
Signed-off-by: Meredith Lancaster <malancas@github.com>
* add download cmd
Signed-off-by: Meredith Lancaster <malancas@github.com>
* add inspect cmd
Signed-off-by: Meredith Lancaster <malancas@github.com>
* pass factory object to inspect cmd, add inspect sub cmd to attestation cmd
Signed-off-by: Meredith Lancaster <malancas@github.com>
* add verify-tuf-root cmd
Signed-off-by: Meredith Lancaster <malancas@github.com>
* pass iostream struct from command
Signed-off-by: Meredith Lancaster <malancas@github.com>
* rename logger pkg to logger
Signed-off-by: Meredith Lancaster <malancas@github.com>
* fix path in codeowners
Signed-off-by: Meredith Lancaster <malancas@github.com>
* formatter
Signed-off-by: Meredith Lancaster <malancas@github.com>
* go mod tidy
Signed-off-by: Meredith Lancaster <malancas@github.com>
* fix printf linter issue
Signed-off-by: Meredith Lancaster <malancas@github.com>
* fix printf linter issue
Signed-off-by: Meredith Lancaster <malancas@github.com>
* check user's GH host for compatibility
Signed-off-by: Meredith Lancaster <malancas@github.com>
* pass oci client to commands directly
Signed-off-by: Meredith Lancaster <malancas@github.com>
* rename command
Signed-off-by: Meredith Lancaster <malancas@github.com>
* mark tuf-root-verify cmd hidden
Signed-off-by: Meredith Lancaster <malancas@github.com>
* move client initialization back to subcommands
Signed-off-by: Meredith Lancaster <malancas@github.com>
* add more verbose options and logging
Signed-off-by: Meredith Lancaster <malancas@github.com>
* add missing logger
Signed-off-by: Meredith Lancaster <malancas@github.com>
* add testing around OCI and API client
Signed-off-by: Meredith Lancaster <malancas@github.com>
* add integration test
Signed-off-by: Meredith Lancaster <malancas@github.com>
* fix file path
Signed-off-by: Meredith Lancaster <malancas@github.com>
* fix command
Signed-off-by: Meredith Lancaster <malancas@github.com>
* build executable before integration test
Signed-off-by: Meredith Lancaster <malancas@github.com>
* split integration tests
Signed-off-by: Meredith Lancaster <malancas@github.com>
* remove integration test steps
Signed-off-by: Meredith Lancaster <malancas@github.com>
* fix flag value
Signed-off-by: Meredith Lancaster <malancas@github.com>
* run integration tests on ubuntu for now
Signed-off-by: Meredith Lancaster <malancas@github.com>
* pull over doc updates
Signed-off-by: Meredith Lancaster <malancas@github.com>
* delete unused test data
Signed-off-by: Meredith Lancaster <malancas@github.com>
* remove Go patch version
Signed-off-by: Meredith Lancaster <malancas@github.com>
* switch assert to require
Signed-off-by: Meredith Lancaster <malancas@github.com>
* rename file
Signed-off-by: Meredith Lancaster <malancas@github.com>
* move integration tests to prexisting test workflow
Signed-off-by: Meredith Lancaster <malancas@github.com>
* use platform matrix for integration tests
Signed-off-by: Meredith Lancaster <malancas@github.com>
* simplify build step
Signed-off-by: Meredith Lancaster <malancas@github.com>
* use StringEnumFlag handling
Signed-off-by: Meredith Lancaster <malancas@github.com>
* typo
Signed-off-by: Meredith Lancaster <malancas@github.com>
* use the iostreams.Test helper func
Signed-off-by: Meredith Lancaster <malancas@github.com>
* create interface for oci client
Signed-off-by: Meredith Lancaster <malancas@github.com>
* add tests for oci client
Signed-off-by: Meredith Lancaster <malancas@github.com>
* rename files
Signed-off-by: Meredith Lancaster <malancas@github.com>
* format file
Signed-off-by: Meredith Lancaster <malancas@github.com>
* fix shellcheck issues
Signed-off-by: Meredith Lancaster <malancas@github.com>
* use testing TempDir method
Signed-off-by: Meredith Lancaster <malancas@github.com>
* cleanup unused tempdir handling
Signed-off-by: Meredith Lancaster <malancas@github.com>
* use table driven tests
Signed-off-by: Meredith Lancaster <malancas@github.com>
* check correct cmd
Signed-off-by: Meredith Lancaster <malancas@github.com>
* support repo option in download sub cmd
Signed-off-by: Meredith Lancaster <malancas@github.com>
* switch over to using RunE
Signed-off-by: Meredith Lancaster <malancas@github.com>
* unexport top level subcommand funcs
Signed-off-by: Meredith Lancaster <malancas@github.com>
* add comment around keychain option
Signed-off-by: Meredith Lancaster <malancas@github.com>
* update comments
Signed-off-by: Meredith Lancaster <malancas@github.com>
* fix inconsistent naming
Signed-off-by: Meredith Lancaster <malancas@github.com>
* add tests for CLI commands
Signed-off-by: Meredith Lancaster <malancas@github.com>
* check for noattestationsfound err
Signed-off-by: Meredith Lancaster <malancas@github.com>
* try out metadata abstraction instead
Signed-off-by: Meredith Lancaster <malancas@github.com>
* switch to using MetadataStore abstraction
Signed-off-by: Meredith Lancaster <malancas@github.com>
* include test case with failing metadata store
Signed-off-by: Meredith Lancaster <malancas@github.com>
* look for err specific to file write
Signed-off-by: Meredith Lancaster <malancas@github.com>
* unexport fields
Signed-off-by: Meredith Lancaster <malancas@github.com>
* return err when an unsupported hash alg is provided
Signed-off-by: Meredith Lancaster <malancas@github.com>
* PrintTableToStdOut returns err when rendering fails
Signed-off-by: Meredith Lancaster <malancas@github.com>
* start adding sigstore verifier unit tests
Signed-off-by: Meredith Lancaster <malancas@github.com>
* add more sigstore verifier specific tests
Signed-off-by: Meredith Lancaster <malancas@github.com>
* use cli table printer
Signed-off-by: Meredith Lancaster <malancas@github.com>
* return JSON results in slice instead of table
Signed-off-by: Meredith Lancaster <malancas@github.com>
* move mock client to test file
Signed-off-by: Meredith Lancaster <malancas@github.com>
* remove unneeded table printer method
Signed-off-by: Meredith Lancaster <malancas@github.com>
* add initial tests for tufrootverify cmd
Signed-off-by: Meredith Lancaster <malancas@github.com>
* formatting
Signed-off-by: Meredith Lancaster <malancas@github.com>
* cleanup method
Signed-off-by: Meredith Lancaster <malancas@github.com>
* close file in error handling branch
Signed-off-by: Meredith Lancaster <malancas@github.com>
* normalize artifact path
Signed-off-by: Meredith Lancaster <malancas@github.com>
* remove unneeded embedded file system
Signed-off-by: Meredith Lancaster <malancas@github.com>
* include image name reference err
Signed-off-by: Meredith Lancaster <malancas@github.com>
* use GH_DEBUG value for io handling
Signed-off-by: Meredith Lancaster <malancas@github.com>
* remove quiet and verbose flags
Signed-off-by: Meredith Lancaster <malancas@github.com>
* add more tufrootveriify tests
Signed-off-by: Meredith Lancaster <malancas@github.com>
* GitHubTUFOptions no longer needs to return error
Signed-off-by: Meredith Lancaster <malancas@github.com>
* remove unneeded slice
Signed-off-by: Meredith Lancaster <malancas@github.com>
* normalize all relative paths
Signed-off-by: Meredith Lancaster <malancas@github.com>
* clean up nil client checks
Signed-off-by: Meredith Lancaster <malancas@github.com>
* set api server based on host
Signed-off-by: Meredith Lancaster <malancas@github.com>
* add comment about http client
Signed-off-by: Meredith Lancaster <malancas@github.com>
* use format flag to handle json output in verify cmd
Signed-off-by: Meredith Lancaster <malancas@github.com>
* use format flag to handle json output
Signed-off-by: Meredith Lancaster <malancas@github.com>
* use normalized path for cli test arg
Signed-off-by: Meredith Lancaster <malancas@github.com>
* add tests for json output
Signed-off-by: Meredith Lancaster <malancas@github.com>
* cleanup error wrapping
Signed-off-by: Meredith Lancaster <malancas@github.com>
* use test fixtures correctly by normalizing path
Signed-off-by: Meredith Lancaster <malancas@github.com>
* dont clean
Signed-off-by: Meredith Lancaster <malancas@github.com>
* escape backwards slash for windows files with replace
Signed-off-by: Meredith Lancaster <malancas@github.com>
* use strings.Split func
Signed-off-by: Meredith Lancaster <malancas@github.com>
* use strings.Replace for all command tests
Signed-off-by: Meredith Lancaster <malancas@github.com>
* use CLI cache dir to store tuf metadata
Signed-off-by: Meredith Lancaster <malancas@github.com>
* 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 <malancas@github.com>
* Revert "add remote verification test"
This reverts commit c0ceb99ca8.
* update json result handling
Signed-off-by: Meredith Lancaster <malancas@github.com>
* add json tags to struct returned by command
Signed-off-by: Meredith Lancaster <malancas@github.com>
* fix how json results are handled
Signed-off-by: Meredith Lancaster <malancas@github.com>
* add test to ensure JSON output is valid
Signed-off-by: Meredith Lancaster <malancas@github.com>
---------
Signed-off-by: Meredith Lancaster <malancas@github.com>
Co-authored-by: Phill MV <phillmv@github.com>
314 lines
8.4 KiB
Go
314 lines
8.4 KiB
Go
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)
|
|
})
|
|
}
|