Merge branch 'trunk' into print-policy-info
This commit is contained in:
commit
862786cca6
10 changed files with 261 additions and 39 deletions
34
README.md
34
README.md
|
|
@ -88,7 +88,7 @@ For more information, see [Linux & BSD installation](./docs/install_linux.md).
|
|||
| ------------------- | --------------------|
|
||||
| `winget install --id GitHub.cli` | `winget upgrade --id GitHub.cli` |
|
||||
|
||||
> **Note**
|
||||
> [!NOTE]
|
||||
> The Windows installer modifies your PATH. When using Windows Terminal, you will need to **open a new window** for the changes to take effect. (Simply opening a new tab will _not_ be sufficient.)
|
||||
|
||||
#### scoop
|
||||
|
|
@ -125,6 +125,38 @@ GitHub CLI comes pre-installed in all [GitHub-Hosted Runners](https://docs.githu
|
|||
|
||||
Download packaged binaries from the [releases page][].
|
||||
|
||||
#### Verification of binaries
|
||||
|
||||
Since version 2.50.0 `gh` has been producing [Build Provenance Attestation](https://github.blog/changelog/2024-06-25-artifact-attestations-is-generally-available/) enabling a cryptographically verifiable paper-trail back to the origin GitHub repository, git revision and build instructions used. The build provenance attestations are signed and relies on Public Good [Sigstore](https://www.sigstore.dev/) for PKI.
|
||||
|
||||
There are two common ways to verify a downloaded release, depending if `gh` is aready installed or not. If `gh` is installed, it's trivial to verify a new release:
|
||||
|
||||
- **Option 1: Using `gh` if already installed:**
|
||||
|
||||
```shell
|
||||
$ % gh at verify -R cli/cli gh_2.62.0_macOS_arm64.zip
|
||||
Loaded digest sha256:fdb77f31b8a6dd23c3fd858758d692a45f7fc76383e37d475bdcae038df92afc for file://gh_2.62.0_macOS_arm64.zip
|
||||
Loaded 1 attestation from GitHub API
|
||||
✓ Verification succeeded!
|
||||
|
||||
sha256:fdb77f31b8a6dd23c3fd858758d692a45f7fc76383e37d475bdcae038df92afc was attested by:
|
||||
REPO PREDICATE_TYPE WORKFLOW
|
||||
cli/cli https://slsa.dev/provenance/v1 .github/workflows/deployment.yml@refs/heads/trunk
|
||||
```
|
||||
|
||||
- **Option 2: Using Sigstore [`cosign`](https://github.com/sigstore/cosign):**
|
||||
|
||||
To perform this, download the [attestation](https://github.com/cli/cli/attestations) for the downloaded release and use cosign to verify the authenticity of the downloaded release:
|
||||
|
||||
```shell
|
||||
$ cosign verify-blob-attestation --bundle cli-cli-attestation-3120304.sigstore.json \
|
||||
--new-bundle-format \
|
||||
--certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
|
||||
--certificate-identity-regexp="^https://github.com/cli/cli/.github/workflows/deployment.yml@refs/heads/trunk$" \
|
||||
gh_2.62.0_macOS_arm64.zip
|
||||
Verified OK
|
||||
```
|
||||
|
||||
### Build from source
|
||||
|
||||
See here on how to [build GitHub CLI from source][build from source].
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ type PullRequest struct {
|
|||
Closed bool
|
||||
URL string
|
||||
BaseRefName string
|
||||
BaseRefOid string
|
||||
HeadRefName string
|
||||
HeadRefOid string
|
||||
Body string
|
||||
|
|
|
|||
|
|
@ -285,6 +285,7 @@ var PullRequestFields = append(sharedIssuePRFields,
|
|||
"additions",
|
||||
"autoMergeRequest",
|
||||
"baseRefName",
|
||||
"baseRefOid",
|
||||
"changedFiles",
|
||||
"commits",
|
||||
"deletions",
|
||||
|
|
|
|||
|
|
@ -13,25 +13,31 @@ var (
|
|||
GitHubTenantOIDCIssuer = "https://token.actions.%s.ghe.com"
|
||||
)
|
||||
|
||||
func VerifyCertExtensions(results []*AttestationProcessingResult, ec EnforcementCriteria) error {
|
||||
// VerifyCertExtensions allows us to perform case insensitive comparisons of certificate extensions
|
||||
func VerifyCertExtensions(results []*AttestationProcessingResult, ec EnforcementCriteria) ([]*AttestationProcessingResult, error) {
|
||||
if len(results) == 0 {
|
||||
return errors.New("no attestations proccessing results")
|
||||
return nil, errors.New("no attestations processing results")
|
||||
}
|
||||
|
||||
verified := make([]*AttestationProcessingResult, 0, len(results))
|
||||
var lastErr error
|
||||
for _, attestation := range results {
|
||||
err := verifyCertExtensions(*attestation.VerificationResult.Signature.Certificate, ec.Certificate)
|
||||
if err == nil {
|
||||
// if at least one attestation is verified, we're good as verification
|
||||
// is defined as successful if at least one attestation is verified
|
||||
return nil
|
||||
if err := verifyCertExtensions(*attestation.VerificationResult.Signature.Certificate, ec.Certificate); err != nil {
|
||||
lastErr = err
|
||||
// move onto the next attestation in the for loop if verification fails
|
||||
continue
|
||||
}
|
||||
lastErr = err
|
||||
// otherwise, add the result to the results slice and increment verifyCount
|
||||
verified = append(verified, attestation)
|
||||
}
|
||||
|
||||
// if we have exited the for loop without returning early due to successful
|
||||
// verification, we need to return an error
|
||||
return lastErr
|
||||
// if we have exited the for loop without verifying any attestations,
|
||||
// return the last error found
|
||||
if len(verified) == 0 {
|
||||
return nil, lastErr
|
||||
}
|
||||
|
||||
return verified, nil
|
||||
}
|
||||
|
||||
func verifyCertExtensions(given, expected certificate.Summary) error {
|
||||
|
|
|
|||
|
|
@ -37,8 +37,9 @@ func TestVerifyCertExtensions(t *testing.T) {
|
|||
}
|
||||
|
||||
t.Run("passes with one result", func(t *testing.T) {
|
||||
err := VerifyCertExtensions(results, c)
|
||||
verified, err := VerifyCertExtensions(results, c)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, verified, 1)
|
||||
})
|
||||
|
||||
t.Run("passes with 1/2 valid results", func(t *testing.T) {
|
||||
|
|
@ -46,8 +47,9 @@ func TestVerifyCertExtensions(t *testing.T) {
|
|||
require.Len(t, twoResults, 2)
|
||||
twoResults[1].VerificationResult.Signature.Certificate.Extensions.SourceRepositoryOwnerURI = "https://github.com/wrong"
|
||||
|
||||
err := VerifyCertExtensions(twoResults, c)
|
||||
verified, err := VerifyCertExtensions(twoResults, c)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, verified, 1)
|
||||
})
|
||||
|
||||
t.Run("fails when all results fail verification", func(t *testing.T) {
|
||||
|
|
@ -56,35 +58,40 @@ func TestVerifyCertExtensions(t *testing.T) {
|
|||
twoResults[0].VerificationResult.Signature.Certificate.Extensions.SourceRepositoryOwnerURI = "https://github.com/wrong"
|
||||
twoResults[1].VerificationResult.Signature.Certificate.Extensions.SourceRepositoryOwnerURI = "https://github.com/wrong"
|
||||
|
||||
err := VerifyCertExtensions(twoResults, c)
|
||||
verified, err := VerifyCertExtensions(twoResults, c)
|
||||
require.Error(t, err)
|
||||
require.Nil(t, verified)
|
||||
})
|
||||
|
||||
t.Run("with wrong SourceRepositoryOwnerURI", func(t *testing.T) {
|
||||
expectedCriteria := c
|
||||
expectedCriteria.Certificate.SourceRepositoryOwnerURI = "https://github.com/wrong"
|
||||
err := VerifyCertExtensions(results, expectedCriteria)
|
||||
verified, err := VerifyCertExtensions(results, expectedCriteria)
|
||||
require.ErrorContains(t, err, "expected SourceRepositoryOwnerURI to be https://github.com/wrong, got https://github.com/owner")
|
||||
require.Nil(t, verified)
|
||||
})
|
||||
|
||||
t.Run("with wrong SourceRepositoryURI", func(t *testing.T) {
|
||||
expectedCriteria := c
|
||||
expectedCriteria.Certificate.SourceRepositoryURI = "https://github.com/foo/wrong"
|
||||
err := VerifyCertExtensions(results, expectedCriteria)
|
||||
verified, err := VerifyCertExtensions(results, expectedCriteria)
|
||||
require.ErrorContains(t, err, "expected SourceRepositoryURI to be https://github.com/foo/wrong, got https://github.com/owner/repo")
|
||||
require.Nil(t, verified)
|
||||
})
|
||||
|
||||
t.Run("with wrong OIDCIssuer", func(t *testing.T) {
|
||||
expectedCriteria := c
|
||||
expectedCriteria.Certificate.Issuer = "wrong"
|
||||
err := VerifyCertExtensions(results, expectedCriteria)
|
||||
verified, err := VerifyCertExtensions(results, expectedCriteria)
|
||||
require.ErrorContains(t, err, "expected Issuer to be wrong, got https://token.actions.githubusercontent.com")
|
||||
require.Nil(t, verified)
|
||||
})
|
||||
|
||||
t.Run("with partial OIDCIssuer match", func(t *testing.T) {
|
||||
expectedResults := results
|
||||
expectedResults[0].VerificationResult.Signature.Certificate.Extensions.Issuer = "https://token.actions.githubusercontent.com/foo-bar"
|
||||
err := VerifyCertExtensions(expectedResults, c)
|
||||
verified, err := VerifyCertExtensions(expectedResults, c)
|
||||
require.ErrorContains(t, err, "expected Issuer to be https://token.actions.githubusercontent.com, got https://token.actions.githubusercontent.com/foo-bar -- if you have a custom OIDC issuer")
|
||||
require.Nil(t, verified)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
|
||||
"github.com/cli/cli/v2/pkg/cmd/attestation/api"
|
||||
"github.com/cli/cli/v2/pkg/cmd/attestation/test/data"
|
||||
"github.com/sigstore/sigstore-go/pkg/bundle"
|
||||
"github.com/sigstore/sigstore-go/pkg/fulcio/certificate"
|
||||
|
||||
in_toto "github.com/in-toto/attestation/go/v1"
|
||||
|
|
@ -13,10 +14,15 @@ import (
|
|||
)
|
||||
|
||||
type MockSigstoreVerifier struct {
|
||||
t *testing.T
|
||||
t *testing.T
|
||||
mockResults []*AttestationProcessingResult
|
||||
}
|
||||
|
||||
func (v *MockSigstoreVerifier) Verify(attestations []*api.Attestation, policy verify.PolicyBuilder) ([]*AttestationProcessingResult, error) {
|
||||
func (v *MockSigstoreVerifier) Verify([]*api.Attestation, verify.PolicyBuilder) ([]*AttestationProcessingResult, error) {
|
||||
if v.mockResults != nil {
|
||||
return v.mockResults, nil
|
||||
}
|
||||
|
||||
statement := &in_toto.Statement{}
|
||||
statement.PredicateType = SLSAPredicateV1
|
||||
|
||||
|
|
@ -45,11 +51,51 @@ func (v *MockSigstoreVerifier) Verify(attestations []*api.Attestation, policy ve
|
|||
}
|
||||
|
||||
func NewMockSigstoreVerifier(t *testing.T) *MockSigstoreVerifier {
|
||||
return &MockSigstoreVerifier{t}
|
||||
result := BuildSigstoreJsMockResult(t)
|
||||
results := []*AttestationProcessingResult{&result}
|
||||
|
||||
return &MockSigstoreVerifier{t, results}
|
||||
}
|
||||
|
||||
func NewMockSigstoreVerifierWithMockResults(t *testing.T, mockResults []*AttestationProcessingResult) *MockSigstoreVerifier {
|
||||
return &MockSigstoreVerifier{t, mockResults}
|
||||
}
|
||||
|
||||
type FailSigstoreVerifier struct{}
|
||||
|
||||
func (v *FailSigstoreVerifier) Verify(attestations []*api.Attestation, policy verify.PolicyBuilder) ([]*AttestationProcessingResult, error) {
|
||||
func (v *FailSigstoreVerifier) Verify([]*api.Attestation, verify.PolicyBuilder) ([]*AttestationProcessingResult, error) {
|
||||
return nil, fmt.Errorf("failed to verify attestations")
|
||||
}
|
||||
|
||||
func BuildMockResult(b *bundle.Bundle, buildSignerURI, sourceRepoOwnerURI, sourceRepoURI, issuer string) AttestationProcessingResult {
|
||||
statement := &in_toto.Statement{}
|
||||
statement.PredicateType = SLSAPredicateV1
|
||||
|
||||
return AttestationProcessingResult{
|
||||
Attestation: &api.Attestation{
|
||||
Bundle: b,
|
||||
},
|
||||
VerificationResult: &verify.VerificationResult{
|
||||
Statement: statement,
|
||||
Signature: &verify.SignatureVerificationResult{
|
||||
Certificate: &certificate.Summary{
|
||||
Extensions: certificate.Extensions{
|
||||
BuildSignerURI: buildSignerURI,
|
||||
SourceRepositoryOwnerURI: sourceRepoOwnerURI,
|
||||
SourceRepositoryURI: sourceRepoURI,
|
||||
Issuer: issuer,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func BuildSigstoreJsMockResult(t *testing.T) AttestationProcessingResult {
|
||||
bundle := data.SigstoreBundle(t)
|
||||
buildSignerURI := "https://github.com/github/example/.github/workflows/release.yml@refs/heads/main"
|
||||
sourceRepoOwnerURI := "https://github.com/sigstore"
|
||||
sourceRepoURI := "https://github.com/sigstore/sigstore-js"
|
||||
issuer := "https://token.actions.githubusercontent.com"
|
||||
return BuildMockResult(bundle, buildSignerURI, sourceRepoOwnerURI, sourceRepoURI, issuer)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,3 +48,26 @@ func getAttestations(o *Options, a artifact.DigestedArtifact) ([]*api.Attestatio
|
|||
msg := fmt.Sprintf("Loaded %s from GitHub API", pluralAttestation)
|
||||
return attestations, msg, nil
|
||||
}
|
||||
|
||||
func verifyAttestations(art artifact.DigestedArtifact, att []*api.Attestation, sgVerifier verification.SigstoreVerifier, ec verification.EnforcementCriteria) ([]*verification.AttestationProcessingResult, string, error) {
|
||||
sgPolicy, err := buildSigstoreVerifyPolicy(ec, art)
|
||||
if err != nil {
|
||||
logMsg := "✗ Failed to build Sigstore verification policy"
|
||||
return nil, logMsg, err
|
||||
}
|
||||
|
||||
sigstoreVerified, err := sgVerifier.Verify(att, sgPolicy)
|
||||
if err != nil {
|
||||
logMsg := "✗ Sigstore verification failed"
|
||||
return nil, logMsg, err
|
||||
}
|
||||
|
||||
// Verify extensions
|
||||
certExtVerified, err := verification.VerifyCertExtensions(sigstoreVerified, ec)
|
||||
if err != nil {
|
||||
logMsg := "✗ Policy verification failed"
|
||||
return nil, logMsg, err
|
||||
}
|
||||
|
||||
return certExtVerified, "", nil
|
||||
}
|
||||
|
|
|
|||
117
pkg/cmd/attestation/verify/attestation_integration_test.go
Normal file
117
pkg/cmd/attestation/verify/attestation_integration_test.go
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
//go:build integration
|
||||
|
||||
package verify
|
||||
|
||||
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/io"
|
||||
"github.com/cli/cli/v2/pkg/cmd/attestation/test"
|
||||
"github.com/cli/cli/v2/pkg/cmd/attestation/verification"
|
||||
"github.com/sigstore/sigstore-go/pkg/fulcio/certificate"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func getAttestationsFor(t *testing.T, bundlePath string) []*api.Attestation {
|
||||
t.Helper()
|
||||
|
||||
attestations, err := verification.GetLocalAttestations(bundlePath)
|
||||
require.NoError(t, err)
|
||||
|
||||
return attestations
|
||||
}
|
||||
|
||||
func TestVerifyAttestations(t *testing.T) {
|
||||
sgVerifier := verification.NewLiveSigstoreVerifier(verification.SigstoreConfig{
|
||||
Logger: io.NewTestHandler(),
|
||||
})
|
||||
|
||||
certSummary := certificate.Summary{}
|
||||
certSummary.SourceRepositoryOwnerURI = "https://github.com/sigstore"
|
||||
certSummary.SourceRepositoryURI = "https://github.com/sigstore/sigstore-js"
|
||||
certSummary.Issuer = verification.GitHubOIDCIssuer
|
||||
|
||||
ec := verification.EnforcementCriteria{
|
||||
Certificate: certSummary,
|
||||
PredicateType: verification.SLSAPredicateV1,
|
||||
SANRegex: "^https://github.com/sigstore/",
|
||||
}
|
||||
require.NoError(t, ec.Valid())
|
||||
|
||||
artifactPath := test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0.tgz")
|
||||
a, err := artifact.NewDigestedArtifact(nil, artifactPath, "sha512")
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("all attestations pass verification", func(t *testing.T) {
|
||||
attestations := getAttestationsFor(t, "../test/data/sigstore-js-2.1.0_with_2_bundles.jsonl")
|
||||
require.Len(t, attestations, 2)
|
||||
results, errMsg, err := verifyAttestations(*a, attestations, sgVerifier, ec)
|
||||
require.NoError(t, err)
|
||||
require.Zero(t, errMsg)
|
||||
require.Len(t, results, 2)
|
||||
})
|
||||
|
||||
t.Run("passes verification with 2/3 attestations passing Sigstore verification", func(t *testing.T) {
|
||||
invalidBundle := getAttestationsFor(t, "../test/data/sigstore-js-2.1.0-bundle-v0.1.json")
|
||||
attestations := getAttestationsFor(t, "../test/data/sigstore-js-2.1.0_with_2_bundles.jsonl")
|
||||
attestations = append(attestations, invalidBundle[0])
|
||||
require.Len(t, attestations, 3)
|
||||
|
||||
results, errMsg, err := verifyAttestations(*a, attestations, sgVerifier, ec)
|
||||
require.NoError(t, err)
|
||||
require.Zero(t, errMsg)
|
||||
require.Len(t, results, 2)
|
||||
})
|
||||
|
||||
t.Run("fails verification when Sigstore verification fails", func(t *testing.T) {
|
||||
invalidBundle := getAttestationsFor(t, "../test/data/sigstore-js-2.1.0-bundle-v0.1.json")
|
||||
invalidBundle2 := getAttestationsFor(t, "../test/data/sigstore-js-2.1.0-bundle-v0.1.json")
|
||||
attestations := append(invalidBundle, invalidBundle2...)
|
||||
require.Len(t, attestations, 2)
|
||||
|
||||
results, errMsg, err := verifyAttestations(*a, attestations, sgVerifier, ec)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, errMsg, "✗ Sigstore verification failed")
|
||||
require.Nil(t, results)
|
||||
})
|
||||
|
||||
t.Run("attestations fail to verify when cert extensions don't match enforcement criteria", func(t *testing.T) {
|
||||
sgjAttestation := getAttestationsFor(t, "../test/data/sigstore-js-2.1.0_with_2_bundles.jsonl")
|
||||
reusableWorkflowAttestations := getAttestationsFor(t, "../test/data/reusable-workflow-attestation.sigstore.json")
|
||||
attestations := []*api.Attestation{sgjAttestation[0], reusableWorkflowAttestations[0], sgjAttestation[1]}
|
||||
require.Len(t, attestations, 3)
|
||||
|
||||
rwfResult := verification.BuildMockResult(reusableWorkflowAttestations[0].Bundle, "", "https://github.com/malancas", "", verification.GitHubOIDCIssuer)
|
||||
sgjResult := verification.BuildSigstoreJsMockResult(t)
|
||||
mockResults := []*verification.AttestationProcessingResult{&sgjResult, &rwfResult, &sgjResult}
|
||||
mockSgVerifier := verification.NewMockSigstoreVerifierWithMockResults(t, mockResults)
|
||||
|
||||
// we want to test that attestations that pass Sigstore verification but fail
|
||||
// cert extension verification are filtered out properly in the second step
|
||||
// in verifyAttestations. By using a mock Sigstore verifier, we can ensure
|
||||
// that the call to verification.VerifyCertExtensions in verifyAttestations
|
||||
// is filtering out attestations as expected
|
||||
results, errMsg, err := verifyAttestations(*a, attestations, mockSgVerifier, ec)
|
||||
require.NoError(t, err)
|
||||
require.Zero(t, errMsg)
|
||||
require.Len(t, results, 2)
|
||||
for _, result := range results {
|
||||
require.NotEqual(t, result.Attestation.Bundle, reusableWorkflowAttestations[0].Bundle)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("fails verification when cert extension verification fails", func(t *testing.T) {
|
||||
attestations := getAttestationsFor(t, "../test/data/sigstore-js-2.1.0_with_2_bundles.jsonl")
|
||||
require.Len(t, attestations, 2)
|
||||
|
||||
expectedCriteria := ec
|
||||
expectedCriteria.Certificate.SourceRepositoryOwnerURI = "https://github.com/wrong"
|
||||
|
||||
results, errMsg, err := verifyAttestations(*a, attestations, sgVerifier, expectedCriteria)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, errMsg, "✗ Policy verification failed")
|
||||
require.Nil(t, results)
|
||||
})
|
||||
}
|
||||
|
|
@ -256,21 +256,9 @@ func runVerify(opts *Options) error {
|
|||
opts.Logger.Println(ec.BuildPolicyInformation())
|
||||
}
|
||||
|
||||
sp, err := buildSigstoreVerifyPolicy(ec, *artifact)
|
||||
verified, errMsg, err := verifyAttestations(*artifact, attestations, opts.SigstoreVerifier, ec)
|
||||
if err != nil {
|
||||
opts.Logger.Println(opts.Logger.ColorScheme.Red("✗ Failed to build Sigstore verification policy"))
|
||||
return err
|
||||
}
|
||||
|
||||
verifyResults, err := opts.SigstoreVerifier.Verify(attestations, sp)
|
||||
if err != nil {
|
||||
opts.Logger.Println(opts.Logger.ColorScheme.Red("✗ Sigstore verification failed"))
|
||||
return err
|
||||
}
|
||||
|
||||
// Verify extensions
|
||||
if err := verification.VerifyCertExtensions(verifyResults, ec); err != nil {
|
||||
opts.Logger.Println(opts.Logger.ColorScheme.Red("✗ Policy verification failed"))
|
||||
opts.Logger.Println(opts.Logger.ColorScheme.Red(errMsg))
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
@ -279,7 +267,7 @@ func runVerify(opts *Options) error {
|
|||
// If an exporter is provided with the --json flag, write the results to the terminal in JSON format
|
||||
if opts.exporter != nil {
|
||||
// print the results to the terminal as an array of JSON objects
|
||||
if err = opts.exporter.Write(opts.Logger.IO, verifyResults); err != nil {
|
||||
if err = opts.exporter.Write(opts.Logger.IO, verified); err != nil {
|
||||
opts.Logger.Println(opts.Logger.ColorScheme.Red("✗ Failed to write JSON output"))
|
||||
return err
|
||||
}
|
||||
|
|
@ -289,7 +277,7 @@ func runVerify(opts *Options) error {
|
|||
opts.Logger.Printf("%s was attested by:\n", artifact.DigestWithAlg())
|
||||
|
||||
// Otherwise print the results to the terminal in a table
|
||||
tableContent, err := buildTableVerifyContent(opts.Tenant, verifyResults)
|
||||
tableContent, err := buildTableVerifyContent(opts.Tenant, verified)
|
||||
if err != nil {
|
||||
opts.Logger.Println(opts.Logger.ColorScheme.Red("failed to parse results"))
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ func TestJSONFields(t *testing.T) {
|
|||
"author",
|
||||
"autoMergeRequest",
|
||||
"baseRefName",
|
||||
"baseRefOid",
|
||||
"body",
|
||||
"changedFiles",
|
||||
"closed",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue