mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-08-10 07:50:08 -04:00
Improve measurements verification with Rekor (#206)
Fetched measurements are now verified using Rekor in addition to a signature check. Signed-off-by: Fabian Kammel <fk@edgeless.systems>
This commit is contained in:
parent
1c29638421
commit
57b8efd1ec
18 changed files with 1320 additions and 322 deletions
|
@ -16,6 +16,7 @@ import (
|
|||
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||
"github.com/edgelesssys/constellation/v2/internal/file"
|
||||
"github.com/edgelesssys/constellation/v2/internal/sigstore"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
@ -42,10 +43,14 @@ type fetchMeasurementsFlags struct {
|
|||
|
||||
func runConfigFetchMeasurements(cmd *cobra.Command, args []string) error {
|
||||
fileHandler := file.NewHandler(afero.NewOsFs())
|
||||
return configFetchMeasurements(cmd, fileHandler, http.DefaultClient)
|
||||
rekor, err := sigstore.NewRekor()
|
||||
if err != nil {
|
||||
return fmt.Errorf("constructing Rekor client: %w", err)
|
||||
}
|
||||
return configFetchMeasurements(cmd, rekor, fileHandler, http.DefaultClient)
|
||||
}
|
||||
|
||||
func configFetchMeasurements(cmd *cobra.Command, fileHandler file.Handler, client *http.Client) error {
|
||||
func configFetchMeasurements(cmd *cobra.Command, verifier rekorVerifier, fileHandler file.Handler, client *http.Client) error {
|
||||
flags, err := parseFetchMeasurementsFlags(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -67,10 +72,16 @@ func configFetchMeasurements(cmd *cobra.Command, fileHandler file.Handler, clien
|
|||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
var fetchedMeasurements config.Measurements
|
||||
if err := fetchedMeasurements.FetchAndVerify(ctx, client, flags.measurementsURL, flags.signatureURL, []byte(constants.CosignPublicKey)); err != nil {
|
||||
hash, err := fetchedMeasurements.FetchAndVerify(ctx, client, flags.measurementsURL, flags.signatureURL, []byte(constants.CosignPublicKey))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := verifyWithRekor(cmd.Context(), verifier, hash); err != nil {
|
||||
cmd.Printf("Ignoring Rekor related error: %v\n", err)
|
||||
cmd.Println("Make sure the downloaded measurements are trustworthy!")
|
||||
}
|
||||
|
||||
conf.UpdateMeasurements(fetchedMeasurements)
|
||||
if err := fileHandler.WriteYAML(flags.config, conf, file.OptOverwrite); err != nil {
|
||||
return err
|
||||
|
|
|
@ -8,6 +8,7 @@ package cmd
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
@ -69,7 +70,7 @@ func TestParseFetchMeasurementsFlags(t *testing.T) {
|
|||
require := require.New(t)
|
||||
|
||||
cmd := newConfigFetchMeasurementsCmd()
|
||||
cmd.Flags().String("config", constants.ConfigFilename, "") // register persisten flag manually
|
||||
cmd.Flags().String("config", constants.ConfigFilename, "") // register persistent flag manually
|
||||
|
||||
if tc.urlFlag != "" {
|
||||
require.NoError(cmd.Flags().Set("url", tc.urlFlag))
|
||||
|
@ -149,9 +150,6 @@ func newTestClient(fn roundTripFunc) *http.Client {
|
|||
}
|
||||
|
||||
func TestConfigFetchMeasurements(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
measurements := `1: fPRxd3lV3uybnSVhcBmM6XLzcvMitXW78G0RRuQxYGc=
|
||||
2: PUWM/lXMA+ofRD8VYr7sjfUcdeFKn8+acjShPxmOeWk=
|
||||
3: PUWM/lXMA+ofRD8VYr7sjfUcdeFKn8+acjShPxmOeWk=
|
||||
|
@ -163,17 +161,6 @@ func TestConfigFetchMeasurements(t *testing.T) {
|
|||
`
|
||||
signature := "MEUCIFdJ5dH6HDywxQWTUh9Bw77wMrq0mNCUjMQGYP+6QsVmAiEAmazj/L7rFGA4/Gz8y+kI5h5E5cDgc3brihvXBKF6qZA="
|
||||
|
||||
cmd := newConfigFetchMeasurementsCmd()
|
||||
cmd.Flags().String("config", constants.ConfigFilename, "") // register persisten flag manually
|
||||
fileHandler := file.NewHandler(afero.NewMemMapFs())
|
||||
|
||||
gcpConfig := config.Default()
|
||||
gcpConfig.RemoveProviderExcept(cloudprovider.GCP)
|
||||
gcpConfig.Provider.GCP.Image = "projects/constellation-images/global/images/constellation-coreos-1658216163"
|
||||
|
||||
err := fileHandler.WriteYAML(constants.ConfigFilename, gcpConfig, file.OptMkdirAll)
|
||||
require.NoError(err)
|
||||
|
||||
client := newTestClient(func(req *http.Request) *http.Response {
|
||||
if req.URL.String() == "https://public-edgeless-constellation.s3.us-east-2.amazonaws.com/projects/constellation-images/global/images/constellation-coreos-1658216163/measurements.yaml" {
|
||||
return &http.Response{
|
||||
|
@ -196,5 +183,43 @@ func TestConfigFetchMeasurements(t *testing.T) {
|
|||
}
|
||||
})
|
||||
|
||||
assert.NoError(configFetchMeasurements(cmd, fileHandler, client))
|
||||
testCases := map[string]struct {
|
||||
verifier rekorVerifier
|
||||
}{
|
||||
"success": {
|
||||
verifier: singleUUIDVerifier(),
|
||||
},
|
||||
"failing search should not result in error": {
|
||||
verifier: &stubRekorVerifier{
|
||||
SearchByHashUUIDs: []string{},
|
||||
SearchByHashError: errors.New("some error"),
|
||||
},
|
||||
},
|
||||
"failing verify should not result in error": {
|
||||
verifier: &stubRekorVerifier{
|
||||
SearchByHashUUIDs: []string{"11111111111111111111111111111111111111111111111111111111111111111111111111111111"},
|
||||
VerifyEntryError: errors.New("some error"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
cmd := newConfigFetchMeasurementsCmd()
|
||||
cmd.Flags().String("config", constants.ConfigFilename, "") // register persistent flag manually
|
||||
fileHandler := file.NewHandler(afero.NewMemMapFs())
|
||||
|
||||
gcpConfig := config.Default()
|
||||
gcpConfig.RemoveProviderExcept(cloudprovider.GCP)
|
||||
gcpConfig.Provider.GCP.Image = "projects/constellation-images/global/images/constellation-coreos-1658216163"
|
||||
|
||||
err := fileHandler.WriteYAML(constants.ConfigFilename, gcpConfig, file.OptMkdirAll)
|
||||
require.NoError(err)
|
||||
|
||||
assert.NoError(configFetchMeasurements(cmd, tc.verifier, fileHandler, client))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||
"github.com/edgelesssys/constellation/v2/internal/file"
|
||||
"github.com/edgelesssys/constellation/v2/internal/sigstore"
|
||||
"github.com/manifoldco/promptui"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/spf13/cobra"
|
||||
|
@ -60,13 +61,17 @@ func runUpgradePlan(cmd *cobra.Command, args []string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rekor, err := sigstore.NewRekor()
|
||||
if err != nil {
|
||||
return fmt.Errorf("constructing Rekor client: %w", err)
|
||||
}
|
||||
|
||||
return upgradePlan(cmd, planner, fileHandler, http.DefaultClient, flags)
|
||||
return upgradePlan(cmd, planner, fileHandler, http.DefaultClient, rekor, flags)
|
||||
}
|
||||
|
||||
// upgradePlan plans an upgrade of a Constellation cluster.
|
||||
func upgradePlan(cmd *cobra.Command, planner upgradePlanner,
|
||||
fileHandler file.Handler, client *http.Client, flags upgradePlanFlags,
|
||||
fileHandler file.Handler, client *http.Client, rekor rekorVerifier, flags upgradePlanFlags,
|
||||
) error {
|
||||
config, err := config.FromFile(fileHandler, flags.configPath)
|
||||
if err != nil {
|
||||
|
@ -93,7 +98,7 @@ func upgradePlan(cmd *cobra.Command, planner upgradePlanner,
|
|||
}
|
||||
|
||||
// get expected measurements for each image
|
||||
if err := getCompatibleImageMeasurements(cmd.Context(), client, []byte(flags.cosignPubKey), compatibleImages); err != nil {
|
||||
if err := getCompatibleImageMeasurements(cmd.Context(), client, rekor, []byte(flags.cosignPubKey), compatibleImages); err != nil {
|
||||
return fmt.Errorf("fetching measurements for compatible images: %w", err)
|
||||
}
|
||||
|
||||
|
@ -174,7 +179,7 @@ func getCompatibleImages(csp cloudprovider.Provider, currentVersion string, imag
|
|||
}
|
||||
|
||||
// getCompatibleImageMeasurements retrieves the expected measurements for each image.
|
||||
func getCompatibleImageMeasurements(ctx context.Context, client *http.Client, pubK []byte, images map[string]config.UpgradeConfig) error {
|
||||
func getCompatibleImageMeasurements(ctx context.Context, client *http.Client, rekor rekorVerifier, pubK []byte, images map[string]config.UpgradeConfig) error {
|
||||
for idx, img := range images {
|
||||
measurementsURL, err := url.Parse(constants.S3PublicBucket + img.Image + "/measurements.yaml")
|
||||
if err != nil {
|
||||
|
@ -186,9 +191,16 @@ func getCompatibleImageMeasurements(ctx context.Context, client *http.Client, pu
|
|||
return err
|
||||
}
|
||||
|
||||
if err := img.Measurements.FetchAndVerify(ctx, client, measurementsURL, signatureURL, pubK); err != nil {
|
||||
hash, err := img.Measurements.FetchAndVerify(ctx, client, measurementsURL, signatureURL, pubK)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = verifyWithRekor(ctx, rekor, hash); err != nil {
|
||||
fmt.Printf("Warning: Unable to verify '%s' in Rekor.\n", hash)
|
||||
fmt.Printf("Make sure measurements are correct.\n")
|
||||
}
|
||||
|
||||
images[idx] = img
|
||||
}
|
||||
|
||||
|
|
|
@ -271,7 +271,7 @@ func TestGetCompatibleImageMeasurements(t *testing.T) {
|
|||
|
||||
pubK := []byte("-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUs5fDUIz9aiwrfr8BK4VjN7jE6sl\ngz7UuXsOin8+dB0SGrbNHy7TJToa2fAiIKPVLTOfvY75DqRAtffhO1fpBA==\n-----END PUBLIC KEY-----")
|
||||
|
||||
err := getCompatibleImageMeasurements(context.Background(), client, pubK, testImages)
|
||||
err := getCompatibleImageMeasurements(context.Background(), client, singleUUIDVerifier(), pubK, testImages)
|
||||
assert.NoError(err)
|
||||
|
||||
for _, image := range testImages {
|
||||
|
@ -295,6 +295,7 @@ func TestUpgradePlan(t *testing.T) {
|
|||
planner stubUpgradePlanner
|
||||
flags upgradePlanFlags
|
||||
csp cloudprovider.Provider
|
||||
verifier rekorVerifier
|
||||
imageFetchStatus int
|
||||
measurementsFetchStatus int
|
||||
wantUpgrade bool
|
||||
|
@ -312,6 +313,7 @@ func TestUpgradePlan(t *testing.T) {
|
|||
cosignPubKey: "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUs5fDUIz9aiwrfr8BK4VjN7jE6sl\ngz7UuXsOin8+dB0SGrbNHy7TJToa2fAiIKPVLTOfvY75DqRAtffhO1fpBA==\n-----END PUBLIC KEY-----",
|
||||
},
|
||||
csp: cloudprovider.GCP,
|
||||
verifier: singleUUIDVerifier(),
|
||||
wantUpgrade: false,
|
||||
},
|
||||
"upgrades gcp": {
|
||||
|
@ -326,6 +328,7 @@ func TestUpgradePlan(t *testing.T) {
|
|||
cosignPubKey: "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUs5fDUIz9aiwrfr8BK4VjN7jE6sl\ngz7UuXsOin8+dB0SGrbNHy7TJToa2fAiIKPVLTOfvY75DqRAtffhO1fpBA==\n-----END PUBLIC KEY-----",
|
||||
},
|
||||
csp: cloudprovider.GCP,
|
||||
verifier: singleUUIDVerifier(),
|
||||
wantUpgrade: true,
|
||||
},
|
||||
"upgrades azure": {
|
||||
|
@ -340,6 +343,7 @@ func TestUpgradePlan(t *testing.T) {
|
|||
cosignPubKey: "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUs5fDUIz9aiwrfr8BK4VjN7jE6sl\ngz7UuXsOin8+dB0SGrbNHy7TJToa2fAiIKPVLTOfvY75DqRAtffhO1fpBA==\n-----END PUBLIC KEY-----",
|
||||
},
|
||||
csp: cloudprovider.Azure,
|
||||
verifier: singleUUIDVerifier(),
|
||||
wantUpgrade: true,
|
||||
},
|
||||
"upgrade to stdout": {
|
||||
|
@ -354,6 +358,7 @@ func TestUpgradePlan(t *testing.T) {
|
|||
cosignPubKey: "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUs5fDUIz9aiwrfr8BK4VjN7jE6sl\ngz7UuXsOin8+dB0SGrbNHy7TJToa2fAiIKPVLTOfvY75DqRAtffhO1fpBA==\n-----END PUBLIC KEY-----",
|
||||
},
|
||||
csp: cloudprovider.GCP,
|
||||
verifier: singleUUIDVerifier(),
|
||||
wantUpgrade: true,
|
||||
},
|
||||
"current image not valid": {
|
||||
|
@ -367,8 +372,9 @@ func TestUpgradePlan(t *testing.T) {
|
|||
filePath: "upgrade-plan.yaml",
|
||||
cosignPubKey: "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUs5fDUIz9aiwrfr8BK4VjN7jE6sl\ngz7UuXsOin8+dB0SGrbNHy7TJToa2fAiIKPVLTOfvY75DqRAtffhO1fpBA==\n-----END PUBLIC KEY-----",
|
||||
},
|
||||
csp: cloudprovider.GCP,
|
||||
wantErr: true,
|
||||
csp: cloudprovider.GCP,
|
||||
verifier: singleUUIDVerifier(),
|
||||
wantErr: true,
|
||||
},
|
||||
"image fetch error": {
|
||||
planner: stubUpgradePlanner{
|
||||
|
@ -381,8 +387,9 @@ func TestUpgradePlan(t *testing.T) {
|
|||
filePath: "upgrade-plan.yaml",
|
||||
cosignPubKey: "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUs5fDUIz9aiwrfr8BK4VjN7jE6sl\ngz7UuXsOin8+dB0SGrbNHy7TJToa2fAiIKPVLTOfvY75DqRAtffhO1fpBA==\n-----END PUBLIC KEY-----",
|
||||
},
|
||||
csp: cloudprovider.GCP,
|
||||
wantErr: true,
|
||||
csp: cloudprovider.GCP,
|
||||
verifier: singleUUIDVerifier(),
|
||||
wantErr: true,
|
||||
},
|
||||
"measurements fetch error": {
|
||||
planner: stubUpgradePlanner{
|
||||
|
@ -395,8 +402,45 @@ func TestUpgradePlan(t *testing.T) {
|
|||
filePath: "upgrade-plan.yaml",
|
||||
cosignPubKey: "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUs5fDUIz9aiwrfr8BK4VjN7jE6sl\ngz7UuXsOin8+dB0SGrbNHy7TJToa2fAiIKPVLTOfvY75DqRAtffhO1fpBA==\n-----END PUBLIC KEY-----",
|
||||
},
|
||||
csp: cloudprovider.GCP,
|
||||
wantErr: true,
|
||||
csp: cloudprovider.GCP,
|
||||
verifier: singleUUIDVerifier(),
|
||||
wantErr: true,
|
||||
},
|
||||
"failing search should not result in error": {
|
||||
planner: stubUpgradePlanner{
|
||||
image: "projects/constellation-images/global/images/constellation-v1-0-0",
|
||||
},
|
||||
imageFetchStatus: http.StatusOK,
|
||||
measurementsFetchStatus: http.StatusOK,
|
||||
flags: upgradePlanFlags{
|
||||
configPath: constants.ConfigFilename,
|
||||
filePath: "upgrade-plan.yaml",
|
||||
cosignPubKey: "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUs5fDUIz9aiwrfr8BK4VjN7jE6sl\ngz7UuXsOin8+dB0SGrbNHy7TJToa2fAiIKPVLTOfvY75DqRAtffhO1fpBA==\n-----END PUBLIC KEY-----",
|
||||
},
|
||||
csp: cloudprovider.GCP,
|
||||
verifier: &stubRekorVerifier{
|
||||
SearchByHashUUIDs: []string{},
|
||||
SearchByHashError: errors.New("some error"),
|
||||
},
|
||||
wantUpgrade: true,
|
||||
},
|
||||
"failing verify should not result in error": {
|
||||
planner: stubUpgradePlanner{
|
||||
image: "projects/constellation-images/global/images/constellation-v1-0-0",
|
||||
},
|
||||
imageFetchStatus: http.StatusOK,
|
||||
measurementsFetchStatus: http.StatusOK,
|
||||
flags: upgradePlanFlags{
|
||||
configPath: constants.ConfigFilename,
|
||||
filePath: "upgrade-plan.yaml",
|
||||
cosignPubKey: "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUs5fDUIz9aiwrfr8BK4VjN7jE6sl\ngz7UuXsOin8+dB0SGrbNHy7TJToa2fAiIKPVLTOfvY75DqRAtffhO1fpBA==\n-----END PUBLIC KEY-----",
|
||||
},
|
||||
csp: cloudprovider.GCP,
|
||||
verifier: &stubRekorVerifier{
|
||||
SearchByHashUUIDs: []string{"11111111111111111111111111111111111111111111111111111111111111111111111111111111"},
|
||||
VerifyEntryError: errors.New("some error"),
|
||||
},
|
||||
wantUpgrade: true,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -445,7 +489,7 @@ func TestUpgradePlan(t *testing.T) {
|
|||
}
|
||||
})
|
||||
|
||||
err := upgradePlan(cmd, tc.planner, fileHandler, client, tc.flags)
|
||||
err := upgradePlan(cmd, tc.planner, fileHandler, client, tc.verifier, tc.flags)
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
return
|
||||
|
|
42
cli/internal/cmd/verifier.go
Normal file
42
cli/internal/cmd/verifier.go
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||
)
|
||||
|
||||
type rekorVerifier interface {
|
||||
SearchByHash(context.Context, string) ([]string, error)
|
||||
VerifyEntry(context.Context, string, string) error
|
||||
}
|
||||
|
||||
func verifyWithRekor(ctx context.Context, verifier rekorVerifier, hash string) error {
|
||||
uuids, err := verifier.SearchByHash(ctx, hash)
|
||||
if err != nil {
|
||||
return fmt.Errorf("searching Rekor for hash: %w", err)
|
||||
}
|
||||
|
||||
if len(uuids) == 0 {
|
||||
return fmt.Errorf("no matching entries in Rekor")
|
||||
}
|
||||
|
||||
// We expect the first entry in Rekor to be our original entry.
|
||||
// SHA256 should ensure there is no entry with the same hash.
|
||||
// Any subsequent hashes are treated as potential attacks and are ignored.
|
||||
// Attacks on Rekor will be monitored from other backend services.
|
||||
artifactUUID := uuids[0]
|
||||
|
||||
return verifier.VerifyEntry(
|
||||
ctx, artifactUUID,
|
||||
base64.StdEncoding.EncodeToString([]byte(constants.CosignPublicKey)),
|
||||
)
|
||||
}
|
36
cli/internal/cmd/verifier_test.go
Normal file
36
cli/internal/cmd/verifier_test.go
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import "context"
|
||||
|
||||
// singleUUIDVerifier constructs a RekorVerifier that returns a single UUID and no errors,
|
||||
// and should work for most tests on the happy path.
|
||||
func singleUUIDVerifier() *stubRekorVerifier {
|
||||
return &stubRekorVerifier{
|
||||
SearchByHashUUIDs: []string{"11111111111111111111111111111111111111111111111111111111111111111111111111111111"},
|
||||
SearchByHashError: nil,
|
||||
VerifyEntryError: nil,
|
||||
}
|
||||
}
|
||||
|
||||
// SubRekorVerifier is a stub for RekorVerifier.
|
||||
type stubRekorVerifier struct {
|
||||
SearchByHashUUIDs []string
|
||||
SearchByHashError error
|
||||
VerifyEntryError error
|
||||
}
|
||||
|
||||
// SearchByHash returns the exported fields SearchByHashUUIDs, SearchByHashError.
|
||||
func (v *stubRekorVerifier) SearchByHash(context.Context, string) ([]string, error) {
|
||||
return v.SearchByHashUUIDs, v.SearchByHashError
|
||||
}
|
||||
|
||||
// VerifyEntry returns the exported field VerifyEntryError.
|
||||
func (v *stubRekorVerifier) VerifyEntry(context.Context, string, string) error {
|
||||
return v.VerifyEntryError
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue