diff --git a/cli/internal/cmd/BUILD.bazel b/cli/internal/cmd/BUILD.bazel index 9b5a8ca95f..78491129d1 100644 --- a/cli/internal/cmd/BUILD.bazel +++ b/cli/internal/cmd/BUILD.bazel @@ -170,7 +170,6 @@ go_test( "//internal/license", "//internal/logger", "//internal/semver", - "//internal/sigstore", "//internal/state", "//internal/versions", "//operators/constellation-node-operator/api/v1alpha1", diff --git a/cli/internal/cmd/configfetchmeasurements.go b/cli/internal/cmd/configfetchmeasurements.go index edd5604bf8..83993b5292 100644 --- a/cli/internal/cmd/configfetchmeasurements.go +++ b/cli/internal/cmd/configfetchmeasurements.go @@ -17,6 +17,8 @@ import ( "github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi" "github.com/edgelesssys/constellation/v2/internal/api/versionsapi" "github.com/edgelesssys/constellation/v2/internal/attestation/measurements" + "github.com/edgelesssys/constellation/v2/internal/attestation/variant" + "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/featureset" @@ -72,10 +74,18 @@ func (f *fetchMeasurementsFlags) parse(flags *pflag.FlagSet) error { return nil } +type verifyFetcher interface { + FetchAndVerifyMeasurements(ctx context.Context, + image string, csp cloudprovider.Provider, attestationVariant variant.Variant, + noVerify bool, + ) (measurements.M, error) +} + type configFetchMeasurementsCmd struct { flags fetchMeasurementsFlags canFetchMeasurements bool log debugLog + verifyFetcher verifyFetcher } func runConfigFetchMeasurements(cmd *cobra.Command, _ []string) error { @@ -89,19 +99,21 @@ func runConfigFetchMeasurements(cmd *cobra.Command, _ []string) error { if err != nil { return fmt.Errorf("constructing Rekor client: %w", err) } - cfm := &configFetchMeasurementsCmd{log: log, canFetchMeasurements: featureset.CanFetchMeasurements} + + verifyFetcher := measurements.NewVerifyFetcher(sigstore.NewCosignVerifier, rekor, http.DefaultClient) + cfm := &configFetchMeasurementsCmd{log: log, canFetchMeasurements: featureset.CanFetchMeasurements, verifyFetcher: verifyFetcher} if err := cfm.flags.parse(cmd.Flags()); err != nil { return fmt.Errorf("parsing flags: %w", err) } cfm.log.Debugf("Using flags %+v", cfm.flags) fetcher := attestationconfigapi.NewFetcherWithClient(http.DefaultClient, constants.CDNRepositoryURL) - return cfm.configFetchMeasurements(cmd, sigstore.NewCosignVerifier, rekor, fileHandler, fetcher, http.DefaultClient) + return cfm.configFetchMeasurements(cmd, fileHandler, fetcher) } func (cfm *configFetchMeasurementsCmd) configFetchMeasurements( - cmd *cobra.Command, newCosignVerifier cosignVerifierConstructor, rekor rekorVerifier, - fileHandler file.Handler, fetcher attestationconfigapi.Fetcher, client *http.Client, + cmd *cobra.Command, + fileHandler file.Handler, fetcher attestationconfigapi.Fetcher, ) error { if !cfm.canFetchMeasurements { cmd.PrintErrln("Fetching measurements is not supported in the OSS build of the Constellation CLI. Consult the documentation for instructions on where to download the enterprise version.") @@ -131,8 +143,8 @@ func (cfm *configFetchMeasurementsCmd) configFetchMeasurements( if err := cfm.flags.updateURLs(conf); err != nil { return err } - verifyFetcher := measurements.NewVerifyFetcher(newCosignVerifier, cfm.flags.insecure, rekor, client) - fetchedMeasurements, err := verifyFetcher.FetchAndVerifyMeasurements(ctx, conf.Image, conf.GetProvider(), conf.GetAttestationConfig().GetVariant()) + fetchedMeasurements, err := cfm.verifyFetcher.FetchAndVerifyMeasurements(ctx, conf.Image, conf.GetProvider(), + conf.GetAttestationConfig().GetVariant(), cfm.flags.insecure) if err != nil { if errors.Is(err, measurements.ErrRekor) { cmd.PrintErrf("Ignoring Rekor related error: %v\n", err) @@ -190,5 +202,3 @@ type rekorVerifier interface { SearchByHash(context.Context, string) ([]string, error) VerifyEntry(context.Context, string, string) error } - -type cosignVerifierConstructor func([]byte) (sigstore.Verifier, error) diff --git a/cli/internal/cmd/configfetchmeasurements_test.go b/cli/internal/cmd/configfetchmeasurements_test.go index ff9f03f94d..d8c7bdaa26 100644 --- a/cli/internal/cmd/configfetchmeasurements_test.go +++ b/cli/internal/cmd/configfetchmeasurements_test.go @@ -7,16 +7,14 @@ SPDX-License-Identifier: AGPL-3.0-only package cmd import ( - "bytes" "context" - "fmt" - "io" "net/http" "net/url" "testing" "github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi" "github.com/edgelesssys/constellation/v2/internal/api/versionsapi" + "github.com/edgelesssys/constellation/v2/internal/attestation/measurements" "github.com/edgelesssys/constellation/v2/internal/attestation/variant" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/config" @@ -153,93 +151,13 @@ func newTestClient(fn roundTripFunc) *http.Client { } func TestConfigFetchMeasurements(t *testing.T) { - measurements := `{ - "version": "v999.999.999", - "ref": "-", - "stream": "stable", - "list": [ - { - "csp": "GCP", - "attestationVariant":"gcp-sev-es", - "measurements": { - "0": { - "expected": "0000000000000000000000000000000000000000000000000000000000000000", - "warnOnly":false - }, - "1": { - "expected": "1111111111111111111111111111111111111111111111111111111111111111", - "warnOnly":false - }, - "2": { - "expected": "2222222222222222222222222222222222222222222222222222222222222222", - "warnOnly":false - }, - "3": { - "expected": "3333333333333333333333333333333333333333333333333333333333333333", - "warnOnly":false - }, - "4": { - "expected": "4444444444444444444444444444444444444444444444444444444444444444", - "warnOnly":false - }, - "5": { - "expected": "5555555555555555555555555555555555555555555555555555555555555555", - "warnOnly":false - }, - "6": { - "expected": "6666666666666666666666666666666666666666666666666666666666666666", - "warnOnly":false - } - } - } - ] -} -` - signature := "placeholder-signature" - - client := newTestClient(func(req *http.Request) *http.Response { - if req.URL.Path == "/constellation/v2/ref/-/stream/stable/v999.999.999/image/measurements.json" { - return &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewBufferString(measurements)), - Header: make(http.Header), - } - } - if req.URL.Path == "/constellation/v2/ref/-/stream/stable/v999.999.999/image/measurements.json.sig" { - return &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewBufferString(signature)), - Header: make(http.Header), - } - } - - fmt.Println("unexpected request", req.URL.String()) - return &http.Response{ - StatusCode: http.StatusNotFound, - Body: io.NopCloser(bytes.NewBufferString("Not found.")), - Header: make(http.Header), - } - }) - testCases := map[string]struct { - cosign cosignVerifierConstructor - rekor rekorVerifier insecureFlag bool wantErr bool + isRekorErr bool }{ - "failing search should not result in error": { - cosign: newStubCosignVerifier, - rekor: &stubRekorVerifier{ - SearchByHashUUIDs: []string{}, - SearchByHashError: assert.AnError, - }, - }, - "failing verify should not result in error": { - cosign: newStubCosignVerifier, - rekor: &stubRekorVerifier{ - SearchByHashUUIDs: []string{"11111111111111111111111111111111111111111111111111111111111111111111111111111111"}, - VerifyEntryError: assert.AnError, - }, + "failing rekor verify should not result in error": { + isRekorErr: true, }, } @@ -256,11 +174,11 @@ func TestConfigFetchMeasurements(t *testing.T) { err := fileHandler.WriteYAML(constants.ConfigFilename, gcpConfig, file.OptMkdirAll) require.NoError(err) - cfm := &configFetchMeasurementsCmd{canFetchMeasurements: true, log: logger.NewTest(t)} + cfm := &configFetchMeasurementsCmd{canFetchMeasurements: true, log: logger.NewTest(t), verifyFetcher: stubVerifyFetcher{isRekorErr: tc.isRekorErr}} cfm.flags.insecure = tc.insecureFlag cfm.flags.force = true - err = cfm.configFetchMeasurements(cmd, tc.cosign, tc.rekor, fileHandler, stubAttestationFetcher{}, client) + err = cfm.configFetchMeasurements(cmd, fileHandler, stubAttestationFetcher{}) if tc.wantErr { assert.Error(err) return @@ -270,6 +188,17 @@ func TestConfigFetchMeasurements(t *testing.T) { } } +type stubVerifyFetcher struct { + isRekorErr bool +} + +func (f stubVerifyFetcher) FetchAndVerifyMeasurements(_ context.Context, _ string, _ cloudprovider.Provider, _ variant.Variant, _ bool) (measurements.M, error) { + if f.isRekorErr { + return nil, measurements.ErrRekor + } + return nil, nil +} + type stubAttestationFetcher struct{} func (f stubAttestationFetcher) FetchSEVSNPVersionList(_ context.Context, _ attestationconfigapi.SEVSNPVersionList) (attestationconfigapi.SEVSNPVersionList, error) { diff --git a/cli/internal/cmd/verifier_test.go b/cli/internal/cmd/verifier_test.go index 1011b23ca6..b55c0ab15b 100644 --- a/cli/internal/cmd/verifier_test.go +++ b/cli/internal/cmd/verifier_test.go @@ -8,8 +8,6 @@ package cmd import ( "context" - - "github.com/edgelesssys/constellation/v2/internal/sigstore" ) // singleUUIDVerifier constructs a RekorVerifier that returns a single UUID and no errors, @@ -43,10 +41,6 @@ type stubCosignVerifier struct { verifyError error } -func newStubCosignVerifier(_ []byte) (sigstore.Verifier, error) { - return &stubCosignVerifier{}, nil -} - func (v *stubCosignVerifier) VerifySignature(_, _ []byte) error { return v.verifyError } diff --git a/internal/api/attestationconfigapi/fetcher.go b/internal/api/attestationconfigapi/fetcher.go index 490edd3f5c..a54e3ebc7c 100644 --- a/internal/api/attestationconfigapi/fetcher.go +++ b/internal/api/attestationconfigapi/fetcher.go @@ -26,7 +26,7 @@ var ErrNoVersionsFound = errors.New("no versions found") type Fetcher interface { FetchSEVSNPVersion(ctx context.Context, version SEVSNPVersionAPI) (SEVSNPVersionAPI, error) FetchSEVSNPVersionList(ctx context.Context, list SEVSNPVersionList) (SEVSNPVersionList, error) - FetchSEVSNPVersionLatest(ctx context.Context, attesation variant.Variant) (SEVSNPVersionAPI, error) + FetchSEVSNPVersionLatest(ctx context.Context, attestation variant.Variant) (SEVSNPVersionAPI, error) } // fetcher fetches AttestationCfg API resources without authentication. diff --git a/internal/attestation/measurements/BUILD.bazel b/internal/attestation/measurements/BUILD.bazel index c197054417..b3b615e19c 100644 --- a/internal/attestation/measurements/BUILD.bazel +++ b/internal/attestation/measurements/BUILD.bazel @@ -27,7 +27,10 @@ go_library( go_test( name = "measurements_test", - srcs = ["measurements_test.go"], + srcs = [ + "fetchmeasurements_test.go", + "measurements_test.go", + ], embed = [":measurements"], deps = [ "//internal/api/versionsapi", diff --git a/internal/attestation/measurements/fetchmeasurements.go b/internal/attestation/measurements/fetchmeasurements.go index 5639d7b581..aa203c56db 100644 --- a/internal/attestation/measurements/fetchmeasurements.go +++ b/internal/attestation/measurements/fetchmeasurements.go @@ -34,22 +34,21 @@ type VerifyFetcher struct { client *http.Client newCosignVerifier cosignVerifierConstructor rekor rekorVerifier - noVerify bool // do not verify measurements } // NewVerifyFetcher creates a new MeasurementFetcher. -func NewVerifyFetcher(newCosignVerifier func([]byte) (sigstore.Verifier, error), noVerify bool, rekor rekorVerifier, client *http.Client) *VerifyFetcher { +func NewVerifyFetcher(newCosignVerifier func([]byte) (sigstore.Verifier, error), rekor rekorVerifier, client *http.Client) *VerifyFetcher { return &VerifyFetcher{ newCosignVerifier: newCosignVerifier, rekor: rekor, client: client, - noVerify: noVerify, } } // 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 { @@ -70,7 +69,7 @@ func (m *VerifyFetcher) FetchAndVerifyMeasurements(ctx context.Context, return nil, err } var fetchedMeasurements M - if m.noVerify { + if noVerify { if err := fetchedMeasurements.FetchNoVerify( ctx, m.client, diff --git a/internal/attestation/measurements/fetchmeasurements_test.go b/internal/attestation/measurements/fetchmeasurements_test.go index dc02ed669a..2bd816d83c 100644 --- a/internal/attestation/measurements/fetchmeasurements_test.go +++ b/internal/attestation/measurements/fetchmeasurements_test.go @@ -142,8 +142,8 @@ func TestFetchMeasurements(t *testing.T) { for name, tc := range testCases { t.Run(name, func(t *testing.T) { assert := assert.New(t) - sut := NewVerifyFetcher(tc.cosign, tc.noVerify, tc.rekor, client) - m, err := sut.FetchAndVerifyMeasurements(context.Background(), "v999.999.999", cloudprovider.GCP, variant.GCPSEVES{}) + sut := NewVerifyFetcher(tc.cosign, tc.rekor, client) + m, err := sut.FetchAndVerifyMeasurements(context.Background(), "v999.999.999", cloudprovider.GCP, variant.GCPSEVES{}, tc.noVerify) if tc.wantErr { assert.Error(err) if tc.isErr != nil { diff --git a/terraform-provider-constellation/internal/provider/BUILD.bazel b/terraform-provider-constellation/internal/provider/BUILD.bazel index ad9a08827e..53e8bba321 100644 --- a/terraform-provider-constellation/internal/provider/BUILD.bazel +++ b/terraform-provider-constellation/internal/provider/BUILD.bazel @@ -18,6 +18,7 @@ go_library( "//internal/cloud/cloudprovider", "//internal/config", "//internal/imagefetcher", + "//internal/sigstore", "//terraform-provider-constellation/internal/data", "@com_github_hashicorp_terraform_plugin_framework//datasource", "@com_github_hashicorp_terraform_plugin_framework//datasource/schema", diff --git a/terraform-provider-constellation/internal/provider/attestation_data_source.go b/terraform-provider-constellation/internal/provider/attestation_data_source.go index 5ed1c8203e..55c1914490 100644 --- a/terraform-provider-constellation/internal/provider/attestation_data_source.go +++ b/terraform-provider-constellation/internal/provider/attestation_data_source.go @@ -176,11 +176,12 @@ func (d *AttestationDataSource) Read(ctx context.Context, req datasource.ReadReq } attestationVariant, err := variant.FromString(data.AttestationVariant.ValueString()) if err != nil { - resp.Diagnostics.AddError("Unknown Attestation Variant", fmt.Sprintf("Unknown Attestation Variant: %s", data.AttestationVariant.ValueString())) + resp.Diagnostics.AddError("Unknown Attestation Variant", + fmt.Sprintf("Unknown Attestation Variant: %s", data.AttestationVariant.ValueString())) return } - if csp == cloudprovider.Azure && attestationVariant.Equal(variant.AzureSEVSNP{}) { - snpVersions, err := d.fetcher.FetchAzureSEVSNPVersionLatest(ctx) + if attestationVariant.Equal(variant.AzureSEVSNP{}) || attestationVariant.Equal(variant.AWSSEVSNP{}) { + snpVersions, err := d.fetcher.FetchSEVSNPVersionLatest(ctx, attestationVariant) if err != nil { resp.Diagnostics.AddError("Fetching SNP Version numbers", err.Error()) return @@ -192,19 +193,16 @@ func (d *AttestationDataSource) Read(ctx context.Context, req datasource.ReadReq return } } - // TODO align with reenable AWS PR - if attestationVariant.Equal(variant.AWSSEVSNP{}) { - resp.Diagnostics.AddWarning("AWS attestation not supported", "AWS is not supported yet") - } rekor, err := sigstore.NewRekor() if err != nil { resp.Diagnostics.AddError("constructing rekor client", err.Error()) return } - verifyFetcher := measurements.NewVerifyFetcher(sigstore.NewCosignVerifier, false, rekor, d.client) + verifyFetcher := measurements.NewVerifyFetcher(sigstore.NewCosignVerifier, rekor, d.client) - fetchedMeasurements, err := verifyFetcher.FetchAndVerifyMeasurements(ctx, data.ImageVersion.ValueString(), csp, attestationVariant) + fetchedMeasurements, err := verifyFetcher.FetchAndVerifyMeasurements(ctx, data.ImageVersion.ValueString(), + csp, attestationVariant, false) if err != nil { if errors.Is(err, measurements.ErrRekor) { resp.Diagnostics.AddWarning("Ignoring Rekor related error", err.Error()) @@ -222,7 +220,9 @@ func (d *AttestationDataSource) Read(ctx context.Context, req datasource.ReadReq tflog.Trace(ctx, "read constellation attestation data source") } -func convertSNPAttestationTfStateCompatible(resp *datasource.ReadResponse, snpVersions attestationconfigapi.AzureSEVSNPVersionAPI) sevSnpAttestation { +func convertSNPAttestationTfStateCompatible(resp *datasource.ReadResponse, + snpVersions attestationconfigapi.SEVSNPVersionAPI, +) sevSnpAttestation { cert, err := config.DefaultForAzureSEVSNP().AMDRootKey.MarshalJSON() if err != nil { resp.Diagnostics.AddError("Marshalling AMD Root Key", err.Error()) diff --git a/terraform-provider-constellation/internal/provider/attestation_data_source_test.go b/terraform-provider-constellation/internal/provider/attestation_data_source_test.go index 8be6770f4f..2a738d0f45 100644 --- a/terraform-provider-constellation/internal/provider/attestation_data_source_test.go +++ b/terraform-provider-constellation/internal/provider/attestation_data_source_test.go @@ -32,6 +32,7 @@ func TestAccAttestationSource(t *testing.T) { Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr("data.constellation_attestation.aws_test", "measurements.0.expected", "7b068c0c3ac29afe264134536b9be26f1d4ccd575b88d3c3ceabf36ac99c0278"), resource.TestCheckResourceAttr("data.constellation_attestation.aws_test", "measurements.0.warn_only", "true"), + resource.TestCheckResourceAttr("data.constellation_attestation.aws_test", "attestation.bootloader", "true"), // TODO(elchead): waiting for attestation from PR. ), },