mirror of
https://github.com/edgelesssys/constellation.git
synced 2024-12-24 23:19:39 -05:00
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>
This commit is contained in:
parent
03c5692fdd
commit
a2de1d23ec
@ -71,7 +71,7 @@ check() {
|
||||
for exclude in "${excludeLockDirs[@]}"; do
|
||||
for i in "${!terraformLockModules[@]}"; do
|
||||
if [[ ${terraformLockModules[i]} == "${BUILD_WORKSPACE_DIRECTORY}/${exclude}"* ]]; then
|
||||
echo " ${terraformLockModules[i]}"
|
||||
echo "${terraformLockModules[i]}"
|
||||
unset 'terraformLockModules[i]'
|
||||
fi
|
||||
done
|
||||
@ -119,6 +119,14 @@ check() {
|
||||
${terraform} -chdir="${module}" fmt -recursive > /dev/null
|
||||
${terraform} -chdir="${module}" validate > /dev/null
|
||||
rm -rf "${module}/.terraform"
|
||||
echo "Deleting lock files in the following directories:" # init generates lockfiles which should only be generated in the generate mode.
|
||||
for dir in "${excludeLockDirs[@]}"; do
|
||||
if [[ -d ${dir} ]]; then
|
||||
find "${dir}" -name '*.lock.hcl' -type f -delete
|
||||
else
|
||||
echo " Directory ${dir} does not exist, skipping"
|
||||
fi
|
||||
done
|
||||
done
|
||||
;;
|
||||
|
||||
|
@ -170,7 +170,6 @@ go_test(
|
||||
"//internal/license",
|
||||
"//internal/logger",
|
||||
"//internal/semver",
|
||||
"//internal/sigstore",
|
||||
"//internal/state",
|
||||
"//internal/versions",
|
||||
"//operators/constellation-node-operator/api/v1alpha1",
|
||||
|
@ -17,12 +17,13 @@ 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"
|
||||
"github.com/edgelesssys/constellation/v2/internal/file"
|
||||
"github.com/edgelesssys/constellation/v2/internal/sigstore"
|
||||
"github.com/edgelesssys/constellation/v2/internal/sigstore/keyselect"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
@ -73,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 {
|
||||
@ -90,19 +99,20 @@ 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.")
|
||||
@ -132,58 +142,16 @@ func (cfm *configFetchMeasurementsCmd) configFetchMeasurements(
|
||||
if err := cfm.flags.updateURLs(conf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfm.log.Debugf("Fetching and verifying measurements")
|
||||
imageVersion, err := versionsapi.NewVersionFromShortPath(conf.Image, versionsapi.VersionKindImage)
|
||||
fetchedMeasurements, err := cfm.verifyFetcher.FetchAndVerifyMeasurements(ctx, conf.Image, conf.GetProvider(),
|
||||
conf.GetAttestationConfig().GetVariant(), cfm.flags.insecure)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
publicKey, err := keyselect.CosignPublicKeyForVersion(imageVersion)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting public key: %w", err)
|
||||
}
|
||||
cosign, err := newCosignVerifier(publicKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating cosign verifier: %w", err)
|
||||
}
|
||||
|
||||
var fetchedMeasurements measurements.M
|
||||
var hash string
|
||||
if cfm.flags.insecure {
|
||||
if err := fetchedMeasurements.FetchNoVerify(
|
||||
ctx,
|
||||
client,
|
||||
cfm.flags.measurementsURL,
|
||||
imageVersion,
|
||||
conf.GetProvider(),
|
||||
conf.GetAttestationConfig().GetVariant(),
|
||||
); err != nil {
|
||||
return fmt.Errorf("fetching measurements without verification: %w", err)
|
||||
}
|
||||
|
||||
cfm.log.Debugf("Fetched measurements without verification")
|
||||
} else {
|
||||
hash, err = fetchedMeasurements.FetchAndVerify(
|
||||
ctx,
|
||||
client,
|
||||
cosign,
|
||||
cfm.flags.measurementsURL,
|
||||
cfm.flags.signatureURL,
|
||||
imageVersion,
|
||||
conf.GetProvider(),
|
||||
conf.GetAttestationConfig().GetVariant(),
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fetching and verifying measurements: %w", err)
|
||||
}
|
||||
cfm.log.Debugf("Fetched and verified measurements, hash is %s", hash)
|
||||
if err := sigstore.VerifyWithRekor(cmd.Context(), publicKey, rekor, hash); err != nil {
|
||||
var rekorErr *measurements.RekorError
|
||||
if errors.As(err, &rekorErr) {
|
||||
cmd.PrintErrf("Ignoring Rekor related error: %v\n", err)
|
||||
cmd.PrintErrln("Make sure the downloaded measurements are trustworthy!")
|
||||
} else {
|
||||
return fmt.Errorf("fetching and verifying measurements: %w", err)
|
||||
}
|
||||
|
||||
cfm.log.Debugf("Verified measurements with Rekor")
|
||||
}
|
||||
cfm.log.Debugf("Measurements:\n", fetchedMeasurements)
|
||||
|
||||
@ -234,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)
|
||||
|
@ -7,23 +7,20 @@ 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"
|
||||
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||
"github.com/edgelesssys/constellation/v2/internal/file"
|
||||
"github.com/edgelesssys/constellation/v2/internal/logger"
|
||||
"github.com/edgelesssys/constellation/v2/internal/sigstore"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@ -154,114 +151,17 @@ 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
|
||||
err error
|
||||
wantErr bool
|
||||
}{
|
||||
"success": {
|
||||
cosign: newStubCosignVerifier,
|
||||
rekor: singleUUIDVerifier(),
|
||||
"no error succeeds": {},
|
||||
"failing rekor verify should not result in error": {
|
||||
err: &measurements.RekorError{},
|
||||
},
|
||||
"success without cosign": {
|
||||
insecureFlag: true,
|
||||
cosign: func(_ []byte) (sigstore.Verifier, error) {
|
||||
return &stubCosignVerifier{
|
||||
verifyError: assert.AnError,
|
||||
}, nil
|
||||
},
|
||||
rekor: singleUUIDVerifier(),
|
||||
},
|
||||
"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,
|
||||
},
|
||||
},
|
||||
"signature verification failure": {
|
||||
cosign: func(_ []byte) (sigstore.Verifier, error) {
|
||||
return &stubCosignVerifier{
|
||||
verifyError: assert.AnError,
|
||||
}, nil
|
||||
},
|
||||
rekor: singleUUIDVerifier(),
|
||||
"error other than Rekor fails": {
|
||||
err: assert.AnError,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
@ -279,11 +179,12 @@ func TestConfigFetchMeasurements(t *testing.T) {
|
||||
|
||||
err := fileHandler.WriteYAML(constants.ConfigFilename, gcpConfig, file.OptMkdirAll)
|
||||
require.NoError(err)
|
||||
cfm := &configFetchMeasurementsCmd{canFetchMeasurements: true, log: logger.NewTest(t)}
|
||||
fetcher := stubVerifyFetcher{err: tc.err}
|
||||
cfm := &configFetchMeasurementsCmd{canFetchMeasurements: true, log: logger.NewTest(t), verifyFetcher: fetcher}
|
||||
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
|
||||
@ -293,6 +194,14 @@ func TestConfigFetchMeasurements(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
type stubVerifyFetcher struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (f stubVerifyFetcher) FetchAndVerifyMeasurements(_ context.Context, _ string, _ cloudprovider.Provider, _ variant.Variant, _ bool) (measurements.M, error) {
|
||||
return nil, f.err
|
||||
}
|
||||
|
||||
type stubAttestationFetcher struct{}
|
||||
|
||||
func (f stubAttestationFetcher) FetchSEVSNPVersionList(_ context.Context, _ attestationconfigapi.SEVSNPVersionList) (attestationconfigapi.SEVSNPVersionList, error) {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -4,6 +4,7 @@ load("//bazel/go:go_test.bzl", "go_test")
|
||||
go_library(
|
||||
name = "measurements",
|
||||
srcs = [
|
||||
"fetchmeasurements.go",
|
||||
"measurements.go",
|
||||
# keep
|
||||
"measurements_enterprise.go",
|
||||
@ -16,6 +17,8 @@ go_library(
|
||||
"//internal/api/versionsapi",
|
||||
"//internal/attestation/variant",
|
||||
"//internal/cloud/cloudprovider",
|
||||
"//internal/sigstore",
|
||||
"//internal/sigstore/keyselect",
|
||||
"@com_github_google_go_tpm//tpmutil",
|
||||
"@com_github_siderolabs_talos_pkg_machinery//config/encoder",
|
||||
"@in_gopkg_yaml_v3//:yaml_v3",
|
||||
@ -24,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",
|
||||
|
113
internal/attestation/measurements/fetchmeasurements.go
Normal file
113
internal/attestation/measurements/fetchmeasurements.go
Normal file
@ -0,0 +1,113 @@
|
||||
/*
|
||||
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
|
||||
}
|
198
internal/attestation/measurements/fetchmeasurements_test.go
Normal file
198
internal/attestation/measurements/fetchmeasurements_test.go
Normal file
@ -0,0 +1,198 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package measurements
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"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/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFetchMeasurements(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":true
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
`
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
noVerify bool
|
||||
wantErr bool
|
||||
asRekorErr bool
|
||||
}{
|
||||
"success": {
|
||||
cosign: newStubCosignVerifier,
|
||||
rekor: singleUUIDVerifier(),
|
||||
},
|
||||
"success without cosign verify": {
|
||||
noVerify: true,
|
||||
cosign: func(_ []byte) (sigstore.Verifier, error) {
|
||||
return &stubCosignVerifier{
|
||||
verifyError: assert.AnError,
|
||||
}, nil
|
||||
},
|
||||
rekor: singleUUIDVerifier(),
|
||||
},
|
||||
"failing search results is ErrRekor": {
|
||||
cosign: newStubCosignVerifier,
|
||||
rekor: &stubRekorVerifier{
|
||||
SearchByHashUUIDs: []string{},
|
||||
SearchByHashError: assert.AnError,
|
||||
},
|
||||
wantErr: true,
|
||||
asRekorErr: true,
|
||||
},
|
||||
"failing verify is ErrRekor": {
|
||||
cosign: newStubCosignVerifier,
|
||||
rekor: &stubRekorVerifier{
|
||||
SearchByHashUUIDs: []string{"11111111111111111111111111111111111111111111111111111111111111111111111111111111"},
|
||||
VerifyEntryError: assert.AnError,
|
||||
},
|
||||
wantErr: true,
|
||||
asRekorErr: true,
|
||||
},
|
||||
"signature verification failure": {
|
||||
cosign: func(_ []byte) (sigstore.Verifier, error) {
|
||||
return &stubCosignVerifier{
|
||||
verifyError: assert.AnError,
|
||||
}, nil
|
||||
},
|
||||
rekor: singleUUIDVerifier(),
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
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.asRekorErr {
|
||||
var rekErr *RekorError
|
||||
assert.ErrorAs(err, &rekErr)
|
||||
}
|
||||
return
|
||||
}
|
||||
assert.NoError(err)
|
||||
// verify example measurements
|
||||
assert.Equal("6666666666666666666666666666666666666666666666666666666666666666", hex.EncodeToString(m[6].Expected))
|
||||
assert.Equal(WarnOnly, m[6].ValidationOpt)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
type stubCosignVerifier struct {
|
||||
verifyError error
|
||||
}
|
||||
|
||||
func newStubCosignVerifier(_ []byte) (sigstore.Verifier, error) {
|
||||
return &stubCosignVerifier{}, nil
|
||||
}
|
||||
|
||||
func (v *stubCosignVerifier) VerifySignature(_, _ []byte) error {
|
||||
return v.verifyError
|
||||
}
|
||||
|
||||
// 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,
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
---
|
||||
# generated by https://github.com/hashicorp/terraform-plugin-docs
|
||||
page_title: "constellation_attestation Data Source - constellation"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
The data source to fetch measurements from a configured cloud provider and image.
|
||||
---
|
||||
|
||||
# constellation_attestation (Data Source)
|
||||
|
||||
The data source to fetch measurements from a configured cloud provider and image.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```terraform
|
||||
data "constellation_attestation" "test" {
|
||||
csp = "aws"
|
||||
attestation_variant = "aws-sev-snp"
|
||||
image_version = "v2.13.0"
|
||||
}
|
||||
```
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
|
||||
### Required
|
||||
|
||||
- `attestation_variant` (String) Attestation variant the image should work with. Can be one of:
|
||||
* `aws-sev-snp`
|
||||
* `aws-nitro-tpm`
|
||||
* `azure-sev-snp`
|
||||
* `gcp-sev-es`
|
||||
- `csp` (String) CSP (Cloud Service Provider) to use. (e.g. `azure`)
|
||||
See the [full list of CSPs](https://docs.edgeless.systems/constellation/overview/clouds) that Constellation supports.
|
||||
- `image_version` (String) The image version to use
|
||||
|
||||
### Optional
|
||||
|
||||
- `maa_url` (String) For Azure only, the URL of the Microsoft Azure Attestation service
|
||||
|
||||
### Read-Only
|
||||
|
||||
- `attestation` (Attributes) Only relevant for SEV-SNP. (see [below for nested schema](#nestedatt--attestation))
|
||||
- `id` (String) The ID of the data source
|
||||
- `measurements` (Attributes Map) (see [below for nested schema](#nestedatt--measurements))
|
||||
|
||||
<a id="nestedatt--attestation"></a>
|
||||
### Nested Schema for `attestation`
|
||||
|
||||
Read-Only:
|
||||
|
||||
- `amd_root_key` (String)
|
||||
- `azure_firmware_signer_config` (Attributes) (see [below for nested schema](#nestedatt--attestation--azure_firmware_signer_config))
|
||||
- `bootloader_version` (Number)
|
||||
- `microcode_version` (Number)
|
||||
- `snp_version` (Number)
|
||||
- `tee_version` (Number)
|
||||
|
||||
<a id="nestedatt--attestation--azure_firmware_signer_config"></a>
|
||||
### Nested Schema for `attestation.azure_firmware_signer_config`
|
||||
|
||||
Read-Only:
|
||||
|
||||
- `accepted_key_digests` (List of String)
|
||||
- `enforcement_policy` (String)
|
||||
- `maa_url` (String)
|
||||
|
||||
|
||||
|
||||
<a id="nestedatt--measurements"></a>
|
||||
### Nested Schema for `measurements`
|
||||
|
||||
Read-Only:
|
||||
|
||||
- `expected` (String)
|
||||
- `warn_only` (Boolean)
|
@ -0,0 +1,5 @@
|
||||
data "constellation_attestation" "test" {
|
||||
csp = "aws"
|
||||
attestation_variant = "aws-sev-snp"
|
||||
image_version = "v2.13.0"
|
||||
}
|
@ -21,6 +21,7 @@ require (
|
||||
github.com/agext/levenshtein v1.2.2 // indirect
|
||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||
github.com/aws/aws-sdk-go v1.44.297 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.18.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.27 // indirect
|
||||
@ -65,6 +66,7 @@ require (
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/go-containerregistry v0.15.2 // indirect
|
||||
github.com/google/go-tpm v0.9.0 // indirect
|
||||
github.com/google/uuid v1.4.0 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.0 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
@ -112,6 +114,7 @@ require (
|
||||
github.com/sassoftware/relic v7.2.1+incompatible // indirect
|
||||
github.com/schollz/progressbar/v3 v3.13.1 // indirect
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.6.0 // indirect
|
||||
github.com/siderolabs/talos/pkg/machinery v1.4.6 // indirect
|
||||
github.com/sigstore/rekor v1.2.2 // indirect
|
||||
github.com/sigstore/sigstore v1.7.1 // indirect
|
||||
github.com/spf13/afero v1.10.0 // indirect
|
||||
|
@ -60,6 +60,8 @@ github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmms
|
||||
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||
github.com/aws/aws-sdk-go v1.44.297 h1:uL4EV0gQxotQVYegIoBqK079328MOJqgG95daFYSkAM=
|
||||
github.com/aws/aws-sdk-go v1.44.297/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
|
||||
github.com/aws/aws-sdk-go-v2 v1.18.1 h1:+tefE750oAb7ZQGzla6bLkOwfcQCEtC5y2RqoqCeqKo=
|
||||
github.com/aws/aws-sdk-go-v2 v1.18.1/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 h1:dK82zF6kkPeCo8J1e+tGx4JdvDIQzj7ygIoLg8WMuGs=
|
||||
@ -291,6 +293,8 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-containerregistry v0.15.2 h1:MMkSh+tjSdnmJZO7ljvEqV1DjfekB6VUEAZgy3a+TQE=
|
||||
github.com/google/go-containerregistry v0.15.2/go.mod h1:wWK+LnOv4jXMM23IT/F1wdYftGWGr47Is8CG+pmHK1Q=
|
||||
github.com/google/go-tpm v0.9.0 h1:sQF6YqWMi+SCXpsmS3fd21oPy/vSddwZry4JnmltHVk=
|
||||
github.com/google/go-tpm v0.9.0/go.mod h1:FkNVkc6C+IsvDI9Jw1OveJmxGZUUaKxtrpOS47QWKfU=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
@ -689,6 +693,7 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
@ -765,6 +770,7 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@ -773,6 +779,7 @@ golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
|
@ -4,6 +4,7 @@ load("//bazel/go:go_test.bzl", "go_test")
|
||||
go_library(
|
||||
name = "provider",
|
||||
srcs = [
|
||||
"attestation_data_source.go",
|
||||
"example_resource.go",
|
||||
"image_data_source.go",
|
||||
"provider.go",
|
||||
@ -11,9 +12,13 @@ go_library(
|
||||
importpath = "github.com/edgelesssys/constellation/v2/terraform-provider-constellation/internal/provider",
|
||||
visibility = ["//terraform-provider-constellation:__subpackages__"],
|
||||
deps = [
|
||||
"//internal/api/attestationconfigapi",
|
||||
"//internal/attestation/measurements",
|
||||
"//internal/attestation/variant",
|
||||
"//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",
|
||||
@ -35,6 +40,7 @@ go_library(
|
||||
go_test(
|
||||
name = "provider_test",
|
||||
srcs = [
|
||||
"attestation_data_source_test.go",
|
||||
"image_data_source_test.go",
|
||||
"provider_test.go",
|
||||
],
|
||||
|
@ -0,0 +1,306 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi"
|
||||
"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/sigstore"
|
||||
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
)
|
||||
|
||||
// Ensure provider defined types fully satisfy framework interfaces.
|
||||
var _ datasource.DataSource = &AttestationDataSource{}
|
||||
|
||||
// NewAttestationDataSource creates a new attestation data source.
|
||||
func NewAttestationDataSource() datasource.DataSource {
|
||||
return &AttestationDataSource{}
|
||||
}
|
||||
|
||||
// AttestationDataSource defines the data source implementation.
|
||||
type AttestationDataSource struct {
|
||||
client *http.Client
|
||||
fetcher attestationconfigapi.Fetcher
|
||||
rekor *sigstore.Rekor
|
||||
}
|
||||
|
||||
// AttestationDataSourceModel describes the data source data model.
|
||||
type AttestationDataSourceModel struct {
|
||||
CSP types.String `tfsdk:"csp"`
|
||||
AttestationVariant types.String `tfsdk:"attestation_variant"`
|
||||
ImageVersion types.String `tfsdk:"image_version"`
|
||||
MaaURL types.String `tfsdk:"maa_url"`
|
||||
ID types.String `tfsdk:"id"`
|
||||
Measurements types.Map `tfsdk:"measurements"`
|
||||
Attestation types.Object `tfsdk:"attestation"`
|
||||
}
|
||||
|
||||
// Configure configures the data source.
|
||||
func (d *AttestationDataSource) Configure(_ context.Context, _ datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
|
||||
d.client = http.DefaultClient
|
||||
d.fetcher = attestationconfigapi.NewFetcher()
|
||||
rekor, err := sigstore.NewRekor()
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("constructing rekor client", err.Error())
|
||||
return
|
||||
}
|
||||
d.rekor = rekor
|
||||
}
|
||||
|
||||
// Metadata returns the metadata for the data source.
|
||||
func (d *AttestationDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_attestation"
|
||||
}
|
||||
|
||||
// Schema returns the schema for the data source.
|
||||
func (d *AttestationDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||
resp.Schema = schema.Schema{
|
||||
// This description is used by the documentation generator and the language server.
|
||||
MarkdownDescription: "The data source to fetch measurements from a configured cloud provider and image.",
|
||||
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"csp": schema.StringAttribute{
|
||||
Description: "CSP (Cloud Service Provider) to use. (e.g. `azure`)",
|
||||
MarkdownDescription: "CSP (Cloud Service Provider) to use. (e.g. `azure`)\n" +
|
||||
"See the [full list of CSPs](https://docs.edgeless.systems/constellation/overview/clouds) that Constellation supports.",
|
||||
Required: true,
|
||||
Validators: []validator.String{
|
||||
stringvalidator.OneOf("aws", "azure", "gcp"),
|
||||
},
|
||||
},
|
||||
"attestation_variant": schema.StringAttribute{
|
||||
Description: "Attestation variant the image should work with. (e.g. `azure-sev-snp`)",
|
||||
MarkdownDescription: "Attestation variant the image should work with. Can be one of:\n" +
|
||||
" * `aws-sev-snp`\n" +
|
||||
" * `aws-nitro-tpm`\n" +
|
||||
" * `azure-sev-snp`\n" +
|
||||
" * `gcp-sev-es`\n",
|
||||
Required: true,
|
||||
Validators: []validator.String{
|
||||
stringvalidator.OneOf("aws-sev-snp", "aws-nitro-tpm", "azure-sev-snp", "gcp-sev-es"),
|
||||
},
|
||||
},
|
||||
"image_version": schema.StringAttribute{
|
||||
MarkdownDescription: "The image version to use",
|
||||
Required: true,
|
||||
},
|
||||
"maa_url": schema.StringAttribute{
|
||||
MarkdownDescription: "For Azure only, the URL of the Microsoft Azure Attestation service",
|
||||
Optional: true,
|
||||
},
|
||||
"id": schema.StringAttribute{
|
||||
Computed: true,
|
||||
MarkdownDescription: "The ID of the data source",
|
||||
},
|
||||
"measurements": schema.MapNestedAttribute{
|
||||
Computed: true,
|
||||
NestedObject: schema.NestedAttributeObject{
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"expected": schema.StringAttribute{
|
||||
Computed: true,
|
||||
},
|
||||
"warn_only": schema.BoolAttribute{
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"attestation": schema.SingleNestedAttribute{
|
||||
Computed: true,
|
||||
MarkdownDescription: "Only relevant for SEV-SNP.",
|
||||
Description: "The values provide sensible defaults. See the docs for advanced usage.", // TODO(elchead): AB#3568
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"bootloader_version": schema.Int64Attribute{
|
||||
Computed: true,
|
||||
},
|
||||
"tee_version": schema.Int64Attribute{
|
||||
Computed: true,
|
||||
},
|
||||
"snp_version": schema.Int64Attribute{
|
||||
Computed: true,
|
||||
},
|
||||
"microcode_version": schema.Int64Attribute{
|
||||
Computed: true,
|
||||
},
|
||||
"azure_firmware_signer_config": schema.SingleNestedAttribute{
|
||||
Computed: true,
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"accepted_key_digests": schema.ListAttribute{
|
||||
Computed: true,
|
||||
ElementType: types.StringType,
|
||||
},
|
||||
"enforcement_policy": schema.StringAttribute{
|
||||
Computed: true,
|
||||
},
|
||||
"maa_url": schema.StringAttribute{
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
"amd_root_key": schema.StringAttribute{
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Read reads from the data source.
|
||||
func (d *AttestationDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
|
||||
var data AttestationDataSourceModel
|
||||
|
||||
// Read Terraform configuration data into the model
|
||||
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
|
||||
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
csp := cloudprovider.FromString(data.CSP.ValueString())
|
||||
if csp == cloudprovider.Unknown {
|
||||
resp.Diagnostics.AddAttributeError(
|
||||
path.Root("csp"),
|
||||
"Invalid CSP",
|
||||
fmt.Sprintf("Invalid CSP: %s", data.CSP.ValueString()),
|
||||
)
|
||||
return
|
||||
}
|
||||
attestationVariant, err := variant.FromString(data.AttestationVariant.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddAttributeError(
|
||||
path.Root("attestation_variant"),
|
||||
"Invalid Attestation Variant",
|
||||
fmt.Sprintf("Invalid attestation variant: %s", data.CSP.ValueString()),
|
||||
)
|
||||
return
|
||||
}
|
||||
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
|
||||
}
|
||||
tfSnpAttestation, err := convertSNPAttestationTfStateCompatible(attestationVariant, snpVersions)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Converting SNP attestation", err.Error())
|
||||
}
|
||||
diags := resp.State.SetAttribute(ctx, path.Root("attestation"), tfSnpAttestation)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
verifyFetcher := measurements.NewVerifyFetcher(sigstore.NewCosignVerifier, d.rekor, d.client)
|
||||
fetchedMeasurements, err := verifyFetcher.FetchAndVerifyMeasurements(ctx, data.ImageVersion.ValueString(),
|
||||
csp, attestationVariant, false)
|
||||
if err != nil {
|
||||
var rekErr *measurements.RekorError
|
||||
if errors.As(err, &rekErr) {
|
||||
resp.Diagnostics.AddWarning("Ignoring Rekor related error", err.Error())
|
||||
} else {
|
||||
resp.Diagnostics.AddError("fetching and verifying measurements", err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
tfMeasurements := convertMeasurementsTfStateCompatible(fetchedMeasurements)
|
||||
diags := resp.State.SetAttribute(ctx, path.Root("measurements"), tfMeasurements)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
tflog.Trace(ctx, "read constellation attestation data source")
|
||||
}
|
||||
|
||||
func convertSNPAttestationTfStateCompatible(attestationVariant variant.Variant,
|
||||
snpVersions attestationconfigapi.SEVSNPVersionAPI,
|
||||
) (tfSnpAttestation sevSnpAttestation, err error) {
|
||||
var cert config.Certificate
|
||||
switch attestationVariant.(type) {
|
||||
case variant.AWSSEVSNP:
|
||||
cert = config.DefaultForAWSSEVSNP().AMDRootKey
|
||||
case variant.AzureSEVSNP:
|
||||
cert = config.DefaultForAzureSEVSNP().AMDRootKey
|
||||
}
|
||||
certBytes, err := cert.MarshalJSON()
|
||||
if err != nil {
|
||||
return tfSnpAttestation, err
|
||||
}
|
||||
tfSnpAttestation = sevSnpAttestation{
|
||||
BootloaderVersion: snpVersions.Bootloader,
|
||||
TEEVersion: snpVersions.TEE,
|
||||
SNPVersion: snpVersions.SNP,
|
||||
MicrocodeVersion: snpVersions.Microcode,
|
||||
AMDRootKey: string(certBytes),
|
||||
}
|
||||
if attestationVariant.Equal(variant.AzureSEVSNP{}) {
|
||||
firmwareCfg := config.DefaultForAzureSEVSNP().FirmwareSignerConfig
|
||||
keyDigestAny, err := firmwareCfg.AcceptedKeyDigests.MarshalYAML()
|
||||
if err != nil {
|
||||
return tfSnpAttestation, err
|
||||
}
|
||||
keyDigest, ok := keyDigestAny.([]string)
|
||||
if !ok {
|
||||
return tfSnpAttestation, errors.New("reading Accepted Key Digests: could not convert to []string")
|
||||
}
|
||||
tfSnpAttestation.AzureSNPFirmwareSignerConfig = azureSnpFirmwareSignerConfig{
|
||||
AcceptedKeyDigests: keyDigest,
|
||||
EnforcementPolicy: firmwareCfg.EnforcementPolicy.String(),
|
||||
MAAURL: firmwareCfg.MAAURL,
|
||||
}
|
||||
}
|
||||
return tfSnpAttestation, nil
|
||||
}
|
||||
|
||||
func convertMeasurementsTfStateCompatible(m measurements.M) map[string]measurement {
|
||||
tfMeasurements := map[string]measurement{}
|
||||
for key, value := range m {
|
||||
keyStr := strconv.FormatUint(uint64(key), 10)
|
||||
tfMeasurements[keyStr] = measurement{
|
||||
Expected: hex.EncodeToString(value.Expected[:]),
|
||||
WarnOnly: bool(value.ValidationOpt),
|
||||
}
|
||||
}
|
||||
return tfMeasurements
|
||||
}
|
||||
|
||||
type measurement struct {
|
||||
Expected string `tfsdk:"expected"`
|
||||
WarnOnly bool `tfsdk:"warn_only"`
|
||||
}
|
||||
|
||||
type sevSnpAttestation struct {
|
||||
BootloaderVersion uint8 `tfsdk:"bootloader_version"`
|
||||
TEEVersion uint8 `tfsdk:"tee_version"`
|
||||
SNPVersion uint8 `tfsdk:"snp_version"`
|
||||
MicrocodeVersion uint8 `tfsdk:"microcode_version"`
|
||||
AMDRootKey string `tfsdk:"amd_root_key"`
|
||||
AzureSNPFirmwareSignerConfig azureSnpFirmwareSignerConfig `tfsdk:"azure_firmware_signer_config"`
|
||||
}
|
||||
|
||||
type azureSnpFirmwareSignerConfig struct {
|
||||
AcceptedKeyDigests []string `tfsdk:"accepted_key_digests"`
|
||||
EnforcementPolicy string `tfsdk:"enforcement_policy"`
|
||||
MAAURL string `tfsdk:"maa_url"`
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
|
||||
)
|
||||
|
||||
func TestAccAttestationSource(t *testing.T) {
|
||||
// Set the path to the Terraform binary for acceptance testing when running under Bazel.
|
||||
bazelPreCheck := func() { bazelSetTerraformBinaryPath(t) }
|
||||
|
||||
testCases := map[string]resource.TestCase{
|
||||
"aws sev-snp succcess": {
|
||||
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
|
||||
PreCheck: bazelPreCheck,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testingConfig + `
|
||||
data "constellation_attestation" "test" {
|
||||
csp = "aws"
|
||||
attestation_variant = "aws-sev-snp"
|
||||
image_version = "v2.13.0"
|
||||
}
|
||||
`,
|
||||
Check: resource.ComposeAggregateTestCheckFunc(
|
||||
resource.TestCheckResourceAttr("data.constellation_attestation.test", "attestation.bootloader_version", "3"),
|
||||
resource.TestCheckResourceAttr("data.constellation_attestation.test", "attestation.microcode_version", "209"),
|
||||
resource.TestCheckResourceAttr("data.constellation_attestation.test", "attestation.snp_version", "20"),
|
||||
resource.TestCheckResourceAttr("data.constellation_attestation.test", "attestation.tee_version", "0"),
|
||||
resource.TestCheckResourceAttr("data.constellation_attestation.test", "attestation.amd_root_key", "\"-----BEGIN CERTIFICATE-----\\nMIIGYzCCBBKgAwIBAgIDAQAAMEYGCSqGSIb3DQEBCjA5oA8wDQYJYIZIAWUDBAIC\\nBQChHDAaBgkqhkiG9w0BAQgwDQYJYIZIAWUDBAICBQCiAwIBMKMDAgEBMHsxFDAS\\nBgNVBAsMC0VuZ2luZWVyaW5nMQswCQYDVQQGEwJVUzEUMBIGA1UEBwwLU2FudGEg\\nQ2xhcmExCzAJBgNVBAgMAkNBMR8wHQYDVQQKDBZBZHZhbmNlZCBNaWNybyBEZXZp\\nY2VzMRIwEAYDVQQDDAlBUkstTWlsYW4wHhcNMjAxMDIyMTcyMzA1WhcNNDUxMDIy\\nMTcyMzA1WjB7MRQwEgYDVQQLDAtFbmdpbmVlcmluZzELMAkGA1UEBhMCVVMxFDAS\\nBgNVBAcMC1NhbnRhIENsYXJhMQswCQYDVQQIDAJDQTEfMB0GA1UECgwWQWR2YW5j\\nZWQgTWljcm8gRGV2aWNlczESMBAGA1UEAwwJQVJLLU1pbGFuMIICIjANBgkqhkiG\\n9w0BAQEFAAOCAg8AMIICCgKCAgEA0Ld52RJOdeiJlqK2JdsVmD7FktuotWwX1fNg\\nW41XY9Xz1HEhSUmhLz9Cu9DHRlvgJSNxbeYYsnJfvyjx1MfU0V5tkKiU1EesNFta\\n1kTA0szNisdYc9isqk7mXT5+KfGRbfc4V/9zRIcE8jlHN61S1ju8X93+6dxDUrG2\\nSzxqJ4BhqyYmUDruPXJSX4vUc01P7j98MpqOS95rORdGHeI52Naz5m2B+O+vjsC0\\n60d37jY9LFeuOP4Meri8qgfi2S5kKqg/aF6aPtuAZQVR7u3KFYXP59XmJgtcog05\\ngmI0T/OitLhuzVvpZcLph0odh/1IPXqx3+MnjD97A7fXpqGd/y8KxX7jksTEzAOg\\nbKAeam3lm+3yKIcTYMlsRMXPcjNbIvmsBykD//xSniusuHBkgnlENEWx1UcbQQrs\\n+gVDkuVPhsnzIRNgYvM48Y+7LGiJYnrmE8xcrexekBxrva2V9TJQqnN3Q53kt5vi\\nQi3+gCfmkwC0F0tirIZbLkXPrPwzZ0M9eNxhIySb2npJfgnqz55I0u33wh4r0ZNQ\\neTGfw03MBUtyuzGesGkcw+loqMaq1qR4tjGbPYxCvpCq7+OgpCCoMNit2uLo9M18\\nfHz10lOMT8nWAUvRZFzteXCm+7PHdYPlmQwUw3LvenJ/ILXoQPHfbkH0CyPfhl1j\\nWhJFZasCAwEAAaN+MHwwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSFrBrRQ/fI\\nrFXUxR1BSKvVeErUUzAPBgNVHRMBAf8EBTADAQH/MDoGA1UdHwQzMDEwL6AtoCuG\\nKWh0dHBzOi8va2RzaW50Zi5hbWQuY29tL3ZjZWsvdjEvTWlsYW4vY3JsMEYGCSqG\\nSIb3DQEBCjA5oA8wDQYJYIZIAWUDBAICBQChHDAaBgkqhkiG9w0BAQgwDQYJYIZI\\nAWUDBAICBQCiAwIBMKMDAgEBA4ICAQC6m0kDp6zv4Ojfgy+zleehsx6ol0ocgVel\\nETobpx+EuCsqVFRPK1jZ1sp/lyd9+0fQ0r66n7kagRk4Ca39g66WGTJMeJdqYriw\\nSTjjDCKVPSesWXYPVAyDhmP5n2v+BYipZWhpvqpaiO+EGK5IBP+578QeW/sSokrK\\ndHaLAxG2LhZxj9aF73fqC7OAJZ5aPonw4RE299FVarh1Tx2eT3wSgkDgutCTB1Yq\\nzT5DuwvAe+co2CIVIzMDamYuSFjPN0BCgojl7V+bTou7dMsqIu/TW/rPCX9/EUcp\\nKGKqPQ3P+N9r1hjEFY1plBg93t53OOo49GNI+V1zvXPLI6xIFVsh+mto2RtgEX/e\\npmMKTNN6psW88qg7c1hTWtN6MbRuQ0vm+O+/2tKBF2h8THb94OvvHHoFDpbCELlq\\nHnIYhxy0YKXGyaW1NjfULxrrmxVW4wcn5E8GddmvNa6yYm8scJagEi13mhGu4Jqh\\n3QU3sf8iUSUr09xQDwHtOQUVIqx4maBZPBtSMf+qUDtjXSSq8lfWcd8bLr9mdsUn\\nJZJ0+tuPMKmBnSH860llKk+VpVQsgqbzDIvOLvD6W1Umq25boxCYJ+TuBoa4s+HH\\nCViAvgT9kf/rBq1d+ivj6skkHxuzcxbk1xv6ZGxrteJxVH7KlX7YRdZ6eARKwLe4\\nAFZEAwoKCQ==\\n-----END CERTIFICATE-----\\n\""),
|
||||
|
||||
resource.TestCheckResourceAttr("data.constellation_attestation.test", "measurements.0.expected", "7b068c0c3ac29afe264134536b9be26f1d4ccd575b88d3c3ceabf36ac99c0278"),
|
||||
resource.TestCheckResourceAttr("data.constellation_attestation.test", "measurements.0.warn_only", "true"),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
"azure sev-snp success": {
|
||||
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
|
||||
PreCheck: bazelPreCheck,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testingConfig + `
|
||||
data "constellation_attestation" "test" {
|
||||
csp = "azure"
|
||||
attestation_variant = "azure-sev-snp"
|
||||
image_version = "v2.13.0"
|
||||
}
|
||||
`,
|
||||
Check: resource.ComposeAggregateTestCheckFunc(
|
||||
resource.TestCheckResourceAttr("data.constellation_attestation.test", "attestation.bootloader_version", "3"),
|
||||
resource.TestCheckResourceAttr("data.constellation_attestation.test", "attestation.microcode_version", "115"),
|
||||
resource.TestCheckResourceAttr("data.constellation_attestation.test", "attestation.snp_version", "8"),
|
||||
resource.TestCheckResourceAttr("data.constellation_attestation.test", "attestation.tee_version", "0"),
|
||||
|
||||
resource.TestCheckResourceAttr("data.constellation_attestation.test", "attestation.azure_firmware_signer_config.accepted_key_digests.0", "0356215882a825279a85b300b0b742931d113bf7e32dde2e50ffde7ec743ca491ecdd7f336dc28a6e0b2bb57af7a44a3"),
|
||||
resource.TestCheckResourceAttr("data.constellation_attestation.test", "attestation.azure_firmware_signer_config.enforcement_policy", "MAAFallback"),
|
||||
|
||||
resource.TestCheckResourceAttr("data.constellation_attestation.test", "attestation.amd_root_key", "\"-----BEGIN CERTIFICATE-----\\nMIIGYzCCBBKgAwIBAgIDAQAAMEYGCSqGSIb3DQEBCjA5oA8wDQYJYIZIAWUDBAIC\\nBQChHDAaBgkqhkiG9w0BAQgwDQYJYIZIAWUDBAICBQCiAwIBMKMDAgEBMHsxFDAS\\nBgNVBAsMC0VuZ2luZWVyaW5nMQswCQYDVQQGEwJVUzEUMBIGA1UEBwwLU2FudGEg\\nQ2xhcmExCzAJBgNVBAgMAkNBMR8wHQYDVQQKDBZBZHZhbmNlZCBNaWNybyBEZXZp\\nY2VzMRIwEAYDVQQDDAlBUkstTWlsYW4wHhcNMjAxMDIyMTcyMzA1WhcNNDUxMDIy\\nMTcyMzA1WjB7MRQwEgYDVQQLDAtFbmdpbmVlcmluZzELMAkGA1UEBhMCVVMxFDAS\\nBgNVBAcMC1NhbnRhIENsYXJhMQswCQYDVQQIDAJDQTEfMB0GA1UECgwWQWR2YW5j\\nZWQgTWljcm8gRGV2aWNlczESMBAGA1UEAwwJQVJLLU1pbGFuMIICIjANBgkqhkiG\\n9w0BAQEFAAOCAg8AMIICCgKCAgEA0Ld52RJOdeiJlqK2JdsVmD7FktuotWwX1fNg\\nW41XY9Xz1HEhSUmhLz9Cu9DHRlvgJSNxbeYYsnJfvyjx1MfU0V5tkKiU1EesNFta\\n1kTA0szNisdYc9isqk7mXT5+KfGRbfc4V/9zRIcE8jlHN61S1ju8X93+6dxDUrG2\\nSzxqJ4BhqyYmUDruPXJSX4vUc01P7j98MpqOS95rORdGHeI52Naz5m2B+O+vjsC0\\n60d37jY9LFeuOP4Meri8qgfi2S5kKqg/aF6aPtuAZQVR7u3KFYXP59XmJgtcog05\\ngmI0T/OitLhuzVvpZcLph0odh/1IPXqx3+MnjD97A7fXpqGd/y8KxX7jksTEzAOg\\nbKAeam3lm+3yKIcTYMlsRMXPcjNbIvmsBykD//xSniusuHBkgnlENEWx1UcbQQrs\\n+gVDkuVPhsnzIRNgYvM48Y+7LGiJYnrmE8xcrexekBxrva2V9TJQqnN3Q53kt5vi\\nQi3+gCfmkwC0F0tirIZbLkXPrPwzZ0M9eNxhIySb2npJfgnqz55I0u33wh4r0ZNQ\\neTGfw03MBUtyuzGesGkcw+loqMaq1qR4tjGbPYxCvpCq7+OgpCCoMNit2uLo9M18\\nfHz10lOMT8nWAUvRZFzteXCm+7PHdYPlmQwUw3LvenJ/ILXoQPHfbkH0CyPfhl1j\\nWhJFZasCAwEAAaN+MHwwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSFrBrRQ/fI\\nrFXUxR1BSKvVeErUUzAPBgNVHRMBAf8EBTADAQH/MDoGA1UdHwQzMDEwL6AtoCuG\\nKWh0dHBzOi8va2RzaW50Zi5hbWQuY29tL3ZjZWsvdjEvTWlsYW4vY3JsMEYGCSqG\\nSIb3DQEBCjA5oA8wDQYJYIZIAWUDBAICBQChHDAaBgkqhkiG9w0BAQgwDQYJYIZI\\nAWUDBAICBQCiAwIBMKMDAgEBA4ICAQC6m0kDp6zv4Ojfgy+zleehsx6ol0ocgVel\\nETobpx+EuCsqVFRPK1jZ1sp/lyd9+0fQ0r66n7kagRk4Ca39g66WGTJMeJdqYriw\\nSTjjDCKVPSesWXYPVAyDhmP5n2v+BYipZWhpvqpaiO+EGK5IBP+578QeW/sSokrK\\ndHaLAxG2LhZxj9aF73fqC7OAJZ5aPonw4RE299FVarh1Tx2eT3wSgkDgutCTB1Yq\\nzT5DuwvAe+co2CIVIzMDamYuSFjPN0BCgojl7V+bTou7dMsqIu/TW/rPCX9/EUcp\\nKGKqPQ3P+N9r1hjEFY1plBg93t53OOo49GNI+V1zvXPLI6xIFVsh+mto2RtgEX/e\\npmMKTNN6psW88qg7c1hTWtN6MbRuQ0vm+O+/2tKBF2h8THb94OvvHHoFDpbCELlq\\nHnIYhxy0YKXGyaW1NjfULxrrmxVW4wcn5E8GddmvNa6yYm8scJagEi13mhGu4Jqh\\n3QU3sf8iUSUr09xQDwHtOQUVIqx4maBZPBtSMf+qUDtjXSSq8lfWcd8bLr9mdsUn\\nJZJ0+tuPMKmBnSH860llKk+VpVQsgqbzDIvOLvD6W1Umq25boxCYJ+TuBoa4s+HH\\nCViAvgT9kf/rBq1d+ivj6skkHxuzcxbk1xv6ZGxrteJxVH7KlX7YRdZ6eARKwLe4\\nAFZEAwoKCQ==\\n-----END CERTIFICATE-----\\n\""),
|
||||
|
||||
resource.TestCheckResourceAttr("data.constellation_attestation.test", "measurements.1.expected", "3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"),
|
||||
resource.TestCheckResourceAttr("data.constellation_attestation.test", "measurements.1.warn_only", "true"),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
"gcp sev-snp succcess": {
|
||||
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
|
||||
PreCheck: bazelPreCheck,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testingConfig + `
|
||||
data "constellation_attestation" "test" {
|
||||
csp = "gcp"
|
||||
attestation_variant = "gcp-sev-es"
|
||||
image_version = "v2.13.0"
|
||||
}
|
||||
`,
|
||||
Check: resource.ComposeAggregateTestCheckFunc(
|
||||
resource.TestCheckResourceAttr("data.constellation_attestation.test", "measurements.1.expected", "745f2fb4235e4647aa0ad5ace781cd929eb68c28870e7dd5d1a1535854325e56"),
|
||||
resource.TestCheckResourceAttr("data.constellation_attestation.test", "measurements.1.warn_only", "true"),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
resource.Test(t, tc)
|
||||
})
|
||||
}
|
||||
}
|
@ -160,8 +160,6 @@ func (d *ImageDataSource) Read(ctx context.Context, req datasource.ReadRequest,
|
||||
|
||||
// Save data into Terraform state
|
||||
data.Reference = types.StringValue(imageRef)
|
||||
// Use a placeholder ID for testing, as per https://developer.hashicorp.com/terraform/plugin/framework/acctests#no-id-found-in-attributes
|
||||
data.ID = types.StringValue("placeholder")
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
|
||||
}
|
||||
|
@ -85,6 +85,6 @@ func (p *ConstellationProvider) Resources(_ context.Context) []func() resource.R
|
||||
// DataSources lists the data sources implemented by the provider.
|
||||
func (p *ConstellationProvider) DataSources(_ context.Context) []func() datasource.DataSource {
|
||||
return []func() datasource.DataSource{
|
||||
NewImageDataSource,
|
||||
NewImageDataSource, NewAttestationDataSource,
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user