cli/pkg/cmd/attestation/verify/verify_test.go
Meredith Lancaster 90b7bf97c5
gh-attestation cmd integration (#8698)
* 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>
2024-04-01 11:13:47 -06:00

401 lines
12 KiB
Go

package verify
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"strings"
"testing"
"github.com/cli/cli/v2/pkg/cmd/attestation/api"
"github.com/cli/cli/v2/pkg/cmd/attestation/artifact/oci"
"github.com/cli/cli/v2/pkg/cmd/attestation/io"
"github.com/cli/cli/v2/pkg/cmd/attestation/test"
"github.com/cli/cli/v2/pkg/cmd/attestation/verification"
"github.com/cli/cli/v2/pkg/cmdutil"
"github.com/cli/cli/v2/pkg/httpmock"
"github.com/cli/cli/v2/pkg/iostreams"
"github.com/stretchr/testify/assert"
"github.com/in-toto/in-toto-golang/in_toto"
"github.com/sigstore/sigstore-go/pkg/verify"
"github.com/stretchr/testify/require"
)
const (
SigstoreSanValue = "https://github.com/sigstore/sigstore-js/.github/workflows/release.yml@refs/heads/main"
SigstoreSanRegex = "^https://github.com/sigstore/sigstore-js/"
)
var (
artifactPath = test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0.tgz")
bundlePath = test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0-bundle.json")
)
func TestNewVerifyCmd(t *testing.T) {
testIO, _, _, _ := iostreams.Test()
f := &cmdutil.Factory{
IOStreams: testIO,
HttpClient: func() (*http.Client, error) {
reg := &httpmock.Registry{}
client := &http.Client{}
httpmock.ReplaceTripper(client, reg)
return client, nil
},
}
testcases := []struct {
name string
cli string
wants Options
wantsErr bool
wantsExporter bool
}{
{
name: "Invalid digest-alg flag",
cli: fmt.Sprintf("%s --bundle %s --digest-alg sha384 --owner sigstore", artifactPath, bundlePath),
wants: Options{
ArtifactPath: test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0.tgz"),
BundlePath: test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0-bundle.json"),
DigestAlgorithm: "sha384",
Limit: 30,
OIDCIssuer: GitHubOIDCIssuer,
Owner: "sigstore",
},
wantsErr: true,
},
{
name: "Use default digest-alg value",
cli: fmt.Sprintf("%s --bundle %s --owner sigstore", artifactPath, bundlePath),
wants: Options{
ArtifactPath: test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0.tgz"),
BundlePath: test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0-bundle.json"),
DigestAlgorithm: "sha256",
Limit: 30,
OIDCIssuer: GitHubOIDCIssuer,
Owner: "sigstore",
SANRegex: "^https://github.com/sigstore/",
},
wantsErr: false,
},
{
name: "Use custom digest-alg value",
cli: fmt.Sprintf("%s --bundle %s --owner sigstore --digest-alg sha512", artifactPath, bundlePath),
wants: Options{
ArtifactPath: test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0.tgz"),
BundlePath: test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0-bundle.json"),
DigestAlgorithm: "sha512",
Limit: 30,
OIDCIssuer: GitHubOIDCIssuer,
Owner: "sigstore",
SANRegex: "^https://github.com/sigstore/",
},
wantsErr: false,
},
{
name: "Missing owner and repo flags",
cli: artifactPath,
wants: Options{
ArtifactPath: test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0.tgz"),
DigestAlgorithm: "sha256",
OIDCIssuer: GitHubOIDCIssuer,
Owner: "sigstore",
Limit: 30,
SANRegex: "^https://github.com/sigstore/",
},
wantsErr: true,
},
{
name: "Has both owner and repo flags",
cli: fmt.Sprintf("%s --owner sigstore --repo sigstore/sigstore-js", artifactPath),
wants: Options{
ArtifactPath: artifactPath,
DigestAlgorithm: "sha256",
OIDCIssuer: GitHubOIDCIssuer,
Owner: "sigstore",
Repo: "sigstore/sigstore-js",
Limit: 30,
},
wantsErr: true,
},
{
name: "Uses default limit flag",
cli: fmt.Sprintf("%s --owner sigstore", artifactPath),
wants: Options{
ArtifactPath: artifactPath,
DigestAlgorithm: "sha256",
Limit: 30,
OIDCIssuer: GitHubOIDCIssuer,
Owner: "sigstore",
SANRegex: "^https://github.com/sigstore/",
},
wantsErr: false,
},
{
name: "Uses custom limit flag",
cli: fmt.Sprintf("%s --owner sigstore --limit 101", artifactPath),
wants: Options{
ArtifactPath: artifactPath,
DigestAlgorithm: "sha256",
OIDCIssuer: GitHubOIDCIssuer,
Owner: "sigstore",
Limit: 101,
SANRegex: "^https://github.com/sigstore/",
},
wantsErr: false,
},
{
name: "Uses invalid limit flag",
cli: fmt.Sprintf("%s --owner sigstore --limit 0", artifactPath),
wants: Options{
ArtifactPath: artifactPath,
DigestAlgorithm: "sha256",
OIDCIssuer: GitHubOIDCIssuer,
Owner: "sigstore",
Limit: 0,
SANRegex: "^https://github.com/sigstore/",
},
wantsErr: true,
},
{
name: "Has both cert-identity and cert-identity-regex flags",
cli: fmt.Sprintf("%s --owner sigstore --cert-identity https://github.com/sigstore/ --cert-identity-regex ^https://github.com/sigstore/", artifactPath),
wants: Options{
ArtifactPath: artifactPath,
DigestAlgorithm: "sha256",
Limit: 30,
OIDCIssuer: GitHubOIDCIssuer,
Owner: "sigstore",
SAN: "https://github.com/sigstore/",
SANRegex: "^https://github.com/sigstore/",
},
wantsErr: true,
},
{
name: "Prints output in JSON format",
cli: fmt.Sprintf("%s --bundle %s --owner sigstore --format json", artifactPath, bundlePath),
wants: Options{
ArtifactPath: artifactPath,
BundlePath: bundlePath,
DigestAlgorithm: "sha256",
Limit: 30,
OIDCIssuer: GitHubOIDCIssuer,
Owner: "sigstore",
SANRegex: "^https://github.com/sigstore/",
},
wantsExporter: true,
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
var opts *Options
cmd := NewVerifyCmd(f, func(o *Options) error {
opts = o
return nil
})
argv := strings.Split(tc.cli, " ")
cmd.SetArgs(argv)
cmd.SetIn(&bytes.Buffer{})
cmd.SetOut(&bytes.Buffer{})
cmd.SetErr(&bytes.Buffer{})
_, err := cmd.ExecuteC()
if tc.wantsErr {
assert.Error(t, err)
return
}
assert.NoError(t, err)
assert.Equal(t, tc.wants.ArtifactPath, opts.ArtifactPath)
assert.Equal(t, tc.wants.BundlePath, opts.BundlePath)
assert.Equal(t, tc.wants.CustomTrustedRoot, opts.CustomTrustedRoot)
assert.Equal(t, tc.wants.DenySelfHostedRunner, opts.DenySelfHostedRunner)
assert.Equal(t, tc.wants.DigestAlgorithm, opts.DigestAlgorithm)
assert.Equal(t, tc.wants.Limit, opts.Limit)
assert.Equal(t, tc.wants.NoPublicGood, opts.NoPublicGood)
assert.Equal(t, tc.wants.OIDCIssuer, opts.OIDCIssuer)
assert.Equal(t, tc.wants.Owner, opts.Owner)
assert.Equal(t, tc.wants.Repo, opts.Repo)
assert.Equal(t, tc.wants.SAN, opts.SAN)
assert.Equal(t, tc.wants.SANRegex, opts.SANRegex)
assert.NotNil(t, opts.APIClient)
assert.NotNil(t, opts.Logger)
assert.NotNil(t, opts.OCIClient)
assert.Equal(t, tc.wantsExporter, opts.exporter != nil)
})
}
}
func TestJSONOutput(t *testing.T) {
testIO, _, out, _ := iostreams.Test()
opts := Options{
ArtifactPath: artifactPath,
BundlePath: bundlePath,
DigestAlgorithm: "sha512",
APIClient: api.NewTestClient(),
Logger: io.NewHandler(testIO),
OCIClient: oci.MockClient{},
OIDCIssuer: GitHubOIDCIssuer,
Owner: "sigstore",
SANRegex: "^https://github.com/sigstore/",
exporter: cmdutil.NewJSONExporter(),
}
require.Nil(t, runVerify(&opts))
var target []*verification.AttestationProcessingResult
err := json.Unmarshal(out.Bytes(), &target)
require.NoError(t, err)
}
func TestRunVerify(t *testing.T) {
logger := io.NewTestHandler()
publicGoodOpts := Options{
ArtifactPath: artifactPath,
BundlePath: bundlePath,
DigestAlgorithm: "sha512",
APIClient: api.NewTestClient(),
Logger: logger,
OCIClient: oci.MockClient{},
OIDCIssuer: GitHubOIDCIssuer,
Owner: "sigstore",
SANRegex: "^https://github.com/sigstore/",
}
t.Run("with valid artifact and bundle", func(t *testing.T) {
require.Nil(t, runVerify(&publicGoodOpts))
})
t.Run("with failing OCI artifact fetch", func(t *testing.T) {
opts := publicGoodOpts
opts.ArtifactPath = "oci://ghcr.io/github/test"
opts.OCIClient = oci.ReferenceFailClient{}
err := runVerify(&opts)
require.Error(t, err)
require.ErrorContains(t, err, "failed to digest artifact")
})
t.Run("with missing artifact path", func(t *testing.T) {
opts := publicGoodOpts
opts.ArtifactPath = "../test/data/non-existent-artifact.zip"
require.Error(t, runVerify(&opts))
})
t.Run("with missing bundle path", func(t *testing.T) {
opts := publicGoodOpts
opts.BundlePath = "../test/data/non-existent-sigstoreBundle.json"
require.Error(t, runVerify(&opts))
})
t.Run("with owner", func(t *testing.T) {
opts := publicGoodOpts
opts.BundlePath = ""
opts.Owner = "sigstore"
require.Nil(t, runVerify(&opts))
})
t.Run("with repo", func(t *testing.T) {
opts := publicGoodOpts
opts.BundlePath = ""
opts.Repo = "github/example"
require.Nil(t, runVerify(&opts))
})
t.Run("with invalid repo", func(t *testing.T) {
opts := publicGoodOpts
opts.BundlePath = ""
opts.Repo = "wrong/example"
opts.APIClient = api.NewFailTestClient()
err := runVerify(&opts)
require.Error(t, err)
require.ErrorContains(t, err, "failed to fetch attestations for subject")
})
t.Run("with invalid owner", func(t *testing.T) {
opts := publicGoodOpts
opts.BundlePath = ""
opts.APIClient = api.NewFailTestClient()
opts.Owner = "wrong-owner"
err := runVerify(&opts)
require.Error(t, err)
require.ErrorContains(t, err, "failed to fetch attestations for subject")
})
t.Run("with invalid OIDC issuer", func(t *testing.T) {
opts := publicGoodOpts
opts.OIDCIssuer = "not-a-real-issuer"
require.Error(t, runVerify(&opts))
})
t.Run("with SAN enforcement", func(t *testing.T) {
opts := Options{
ArtifactPath: artifactPath,
BundlePath: bundlePath,
APIClient: api.NewTestClient(),
DigestAlgorithm: "sha512",
Logger: logger,
OIDCIssuer: GitHubOIDCIssuer,
Owner: "sigstore",
SAN: SigstoreSanValue,
}
require.Nil(t, runVerify(&opts))
})
t.Run("with invalid SAN", func(t *testing.T) {
opts := publicGoodOpts
opts.SAN = "fake san"
require.Error(t, runVerify(&opts))
})
t.Run("with SAN regex enforcement", func(t *testing.T) {
opts := publicGoodOpts
opts.SANRegex = SigstoreSanRegex
require.Nil(t, runVerify(&opts))
})
t.Run("with invalid SAN regex", func(t *testing.T) {
opts := publicGoodOpts
opts.SANRegex = "^https://github.com/sigstore/not-real/"
require.Error(t, runVerify(&opts))
})
t.Run("with no matching OIDC issuer", func(t *testing.T) {
opts := publicGoodOpts
opts.OIDCIssuer = "some-other-issuer"
require.Error(t, runVerify(&opts))
})
t.Run("with missing API client", func(t *testing.T) {
customOpts := publicGoodOpts
customOpts.APIClient = nil
customOpts.BundlePath = ""
require.Error(t, runVerify(&customOpts))
})
}
func TestVerifySLSAPredicateType_InvalidPredicate(t *testing.T) {
statement := &in_toto.Statement{}
statement.PredicateType = "some-other-predicate-type"
apr := []*verification.AttestationProcessingResult{
{
VerificationResult: &verify.VerificationResult{
Statement: statement,
},
},
}
err := verifySLSAPredicateType(io.NewTestHandler(), apr)
require.Error(t, err)
require.ErrorIs(t, err, ErrNoMatchingSLSAPredicate)
}