constellation/internal/attestation/measurements/fetchmeasurements.go
Adrian Stobbe a2de1d23ec
terraform-provider: add attestation data source (#2640)
Signed-off-by: Daniel Weiße <dw@edgeless.systems>
Co-authored-by: Daniel Weiße <dw@edgeless.systems>
2023-11-28 17:30:11 +01:00

114 lines
3.1 KiB
Go

/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package measurements
import (
"context"
"fmt"
"net/http"
"github.com/edgelesssys/constellation/v2/internal/api/versionsapi"
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/sigstore"
"github.com/edgelesssys/constellation/v2/internal/sigstore/keyselect"
)
// RekorError is returned when verifying measurements with Rekor fails.
type RekorError struct {
err error
}
// Error returns the error message.
func (e *RekorError) Error() string {
return fmt.Sprintf("verifying measurements with Rekor failed: %s", e.err)
}
// Unwrap returns the wrapped error.
func (e *RekorError) Unwrap() error {
return e.err
}
// VerifyFetcher is a high-level fetcher that fetches measurements and verifies them.
type VerifyFetcher struct {
client *http.Client
newCosignVerifier cosignVerifierConstructor
rekor rekorVerifier
}
// NewVerifyFetcher creates a new MeasurementFetcher.
func NewVerifyFetcher(newCosignVerifier func([]byte) (sigstore.Verifier, error), rekor rekorVerifier, client *http.Client) *VerifyFetcher {
return &VerifyFetcher{
newCosignVerifier: newCosignVerifier,
rekor: rekor,
client: client,
}
}
// FetchAndVerifyMeasurements fetches and verifies measurements for the given version and attestation variant.
func (m *VerifyFetcher) FetchAndVerifyMeasurements(ctx context.Context,
image string, csp cloudprovider.Provider, attestationVariant variant.Variant,
noVerify bool,
) (M, error) {
version, err := versionsapi.NewVersionFromShortPath(image, versionsapi.VersionKindImage)
if err != nil {
return nil, fmt.Errorf("parsing image version: %w", err)
}
publicKey, err := keyselect.CosignPublicKeyForVersion(version)
if err != nil {
return nil, fmt.Errorf("getting public key: %w", err)
}
cosign, err := m.newCosignVerifier(publicKey)
if err != nil {
return nil, fmt.Errorf("creating cosign verifier: %w", err)
}
measurementsURL, signatureURL, err := versionsapi.MeasurementURL(version)
if err != nil {
return nil, err
}
var fetchedMeasurements M
if noVerify {
if err := fetchedMeasurements.FetchNoVerify(
ctx,
m.client,
measurementsURL,
version,
csp,
attestationVariant,
); err != nil {
return nil, fmt.Errorf("fetching measurements: %w", err)
}
} else {
hash, err := fetchedMeasurements.FetchAndVerify(
ctx,
m.client,
cosign,
measurementsURL,
signatureURL,
version,
csp,
attestationVariant,
)
if err != nil {
return nil, fmt.Errorf("fetching and verifying measurements: %w", err)
}
if err := sigstore.VerifyWithRekor(ctx, publicKey, m.rekor, hash); err != nil {
return nil, &RekorError{err: err}
}
}
return fetchedMeasurements, nil
}
type cosignVerifierConstructor func([]byte) (sigstore.Verifier, error)
type rekorVerifier interface {
SearchByHash(context.Context, string) ([]string, error)
VerifyEntry(context.Context, string, string) error
}