mirror of
https://github.com/edgelesssys/constellation.git
synced 2024-12-24 06:59:40 -05:00
cli: image measurements (v2)
This commit is contained in:
parent
2ebc0cf2c8
commit
e5b394db87
@ -100,16 +100,19 @@ func (cfm *configFetchMeasurementsCmd) configFetchMeasurements(
|
||||
}
|
||||
|
||||
cfm.log.Debugf("Fetching and verifying measurements")
|
||||
imageVersion, err := versionsapi.NewVersionFromShortPath(conf.Image, versionsapi.VersionKindImage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var fetchedMeasurements measurements.M
|
||||
hash, err := fetchedMeasurements.FetchAndVerify(
|
||||
ctx, client,
|
||||
flags.measurementsURL,
|
||||
flags.signatureURL,
|
||||
cosignPublicKey,
|
||||
measurements.WithMetadata{
|
||||
CSP: conf.GetProvider(),
|
||||
Image: conf.Image,
|
||||
},
|
||||
imageVersion,
|
||||
conf.GetProvider(),
|
||||
conf.GetAttestationConfig().GetVariant(),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -182,7 +185,7 @@ func (f *fetchMeasurementsFlags) updateURLs(conf *config.Config) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating version from image name: %w", err)
|
||||
}
|
||||
measurementsURL, signatureURL, err := versionsapi.MeasurementURL(ver, conf.GetProvider())
|
||||
measurementsURL, signatureURL, err := versionsapi.MeasurementURL(ver)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -119,8 +119,8 @@ func TestUpdateURLs(t *testing.T) {
|
||||
},
|
||||
},
|
||||
flags: &fetchMeasurementsFlags{},
|
||||
wantMeasurementsURL: ver.ArtifactsURL() + "/image/csp/gcp/measurements.json",
|
||||
wantMeasurementsSigURL: ver.ArtifactsURL() + "/image/csp/gcp/measurements.json.sig",
|
||||
wantMeasurementsURL: ver.ArtifactsURL("v2") + "/image/measurements.json",
|
||||
wantMeasurementsSigURL: ver.ArtifactsURL("v2") + "/image/measurements.json.sig",
|
||||
},
|
||||
"both set by user": {
|
||||
conf: &config.Config{
|
||||
@ -181,30 +181,58 @@ func TestConfigFetchMeasurements(t *testing.T) {
|
||||
cosignPublicKey := []byte("-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEu78QgxOOcao6U91CSzEXxrKhvFTt\nJHNy+eX6EMePtDm8CnDF9HSwnTlD0itGJ/XHPQA5YX10fJAqI1y+ehlFMw==\n-----END PUBLIC KEY-----")
|
||||
|
||||
measurements := `{
|
||||
"csp": "gcp",
|
||||
"image": "v999.999.999",
|
||||
"measurements": {
|
||||
"0": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"1": "1111111111111111111111111111111111111111111111111111111111111111",
|
||||
"2": "2222222222222222222222222222222222222222222222222222222222222222",
|
||||
"3": "3333333333333333333333333333333333333333333333333333333333333333",
|
||||
"4": "4444444444444444444444444444444444444444444444444444444444444444",
|
||||
"5": "5555555555555555555555555555555555555555555555555555555555555555",
|
||||
"6": "6666666666666666666666666666666666666666666666666666666666666666"
|
||||
}
|
||||
"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 := "MEYCIQDRAQNK2NjHJBGrnw3HQAyBsXMCmVCptBdgA6VZ3IlyiAIhAPG42waF1aFZq7dnjP3b2jsMNUtaKYDQQSazW1AX8jgF"
|
||||
signature := "MEUCIHQETkvMRy8WaWMroX4Aa2J86bTW0kGMp8NG0YLXJKZJAiEA7ZdxoQzSTyBFNhZ1bwB5eT3av0biAdb66dJRFxQlKLA="
|
||||
|
||||
client := newTestClient(func(req *http.Request) *http.Response {
|
||||
if req.URL.Path == "/constellation/v1/ref/-/stream/stable/v999.999.999/image/csp/gcp/measurements.json" {
|
||||
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/v1/ref/-/stream/stable/v999.999.999/image/csp/gcp/measurements.json.sig" {
|
||||
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)),
|
||||
|
@ -26,6 +26,7 @@ import (
|
||||
"github.com/edgelesssys/constellation/v2/internal/kubernetes/kubectl"
|
||||
conSemver "github.com/edgelesssys/constellation/v2/internal/semver"
|
||||
"github.com/edgelesssys/constellation/v2/internal/sigstore"
|
||||
"github.com/edgelesssys/constellation/v2/internal/variant"
|
||||
"github.com/edgelesssys/constellation/v2/internal/versions"
|
||||
"github.com/edgelesssys/constellation/v2/internal/versionsapi"
|
||||
"github.com/edgelesssys/constellation/v2/internal/versionsapi/fetcher"
|
||||
@ -139,7 +140,8 @@ func (u *upgradeCheckCmd) upgradeCheck(cmd *cobra.Command, fileHandler file.Hand
|
||||
u.log.Debugf("Read configuration from %q", flags.configPath)
|
||||
// get current image version of the cluster
|
||||
csp := conf.GetProvider()
|
||||
u.log.Debugf("Using provider %s", csp.String())
|
||||
attestationVariant := conf.GetAttestationConfig().GetVariant()
|
||||
u.log.Debugf("Using provider %s with attestation variant %s", csp.String(), attestationVariant.String())
|
||||
|
||||
current, err := u.collect.currentVersions(cmd.Context())
|
||||
if err != nil {
|
||||
@ -167,7 +169,7 @@ func (u *upgradeCheckCmd) upgradeCheck(cmd *cobra.Command, fileHandler file.Hand
|
||||
semver.Sort(newKubernetes)
|
||||
|
||||
supported.image = filterImageUpgrades(current.image, supported.image)
|
||||
newImages, err := u.collect.newMeasurements(cmd.Context(), csp, supported.image)
|
||||
newImages, err := u.collect.newMeasurements(cmd.Context(), csp, attestationVariant, supported.image)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -240,7 +242,7 @@ type collector interface {
|
||||
currentVersions(ctx context.Context) (currentVersionInfo, error)
|
||||
supportedVersions(ctx context.Context, version, currentK8sVersion string) (supportedVersionInfo, error)
|
||||
newImages(ctx context.Context, version string) ([]versionsapi.Version, error)
|
||||
newMeasurements(ctx context.Context, csp cloudprovider.Provider, images []versionsapi.Version) (map[string]measurements.M, error)
|
||||
newMeasurements(ctx context.Context, csp cloudprovider.Provider, attestationVariant variant.Variant, images []versionsapi.Version) (map[string]measurements.M, error)
|
||||
newerVersions(ctx context.Context, allowedVersions []string) ([]versionsapi.Version, error)
|
||||
newCLIVersions(ctx context.Context) ([]string, error)
|
||||
filterCompatibleCLIVersions(ctx context.Context, cliPatchVersions []string, currentK8sVersion string) ([]string, error)
|
||||
@ -259,9 +261,9 @@ type versionCollector struct {
|
||||
log debugLog
|
||||
}
|
||||
|
||||
func (v *versionCollector) newMeasurements(ctx context.Context, csp cloudprovider.Provider, images []versionsapi.Version) (map[string]measurements.M, error) {
|
||||
func (v *versionCollector) newMeasurements(ctx context.Context, csp cloudprovider.Provider, attestationVariant variant.Variant, images []versionsapi.Version) (map[string]measurements.M, error) {
|
||||
// get expected measurements for each image
|
||||
upgrades, err := getCompatibleImageMeasurements(ctx, v.writer, v.client, v.rekor, []byte(v.flags.cosignPubKey), csp, images, v.log)
|
||||
upgrades, err := getCompatibleImageMeasurements(ctx, v.writer, v.client, v.rekor, []byte(v.flags.cosignPubKey), csp, attestationVariant, images, v.log)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fetching measurements for compatible images: %w", err)
|
||||
}
|
||||
@ -524,13 +526,13 @@ func getCurrentKubernetesVersion(ctx context.Context, checker upgradeChecker) (s
|
||||
|
||||
// getCompatibleImageMeasurements retrieves the expected measurements for each image.
|
||||
func getCompatibleImageMeasurements(ctx context.Context, writer io.Writer, client *http.Client, rekor rekorVerifier, pubK []byte,
|
||||
csp cloudprovider.Provider, versions []versionsapi.Version, log debugLog,
|
||||
csp cloudprovider.Provider, attestationVariant variant.Variant, versions []versionsapi.Version, log debugLog,
|
||||
) (map[string]measurements.M, error) {
|
||||
upgrades := make(map[string]measurements.M)
|
||||
for _, version := range versions {
|
||||
log.Debugf("Fetching measurements for image: %s", version)
|
||||
shortPath := version.ShortPath()
|
||||
measurementsURL, signatureURL, err := versionsapi.MeasurementURL(version, csp)
|
||||
measurementsURL, signatureURL, err := versionsapi.MeasurementURL(version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -542,10 +544,9 @@ func getCompatibleImageMeasurements(ctx context.Context, writer io.Writer, clien
|
||||
measurementsURL,
|
||||
signatureURL,
|
||||
pubK,
|
||||
measurements.WithMetadata{
|
||||
CSP: csp,
|
||||
Image: shortPath,
|
||||
},
|
||||
version,
|
||||
csp,
|
||||
attestationVariant,
|
||||
)
|
||||
if err != nil {
|
||||
if _, err := fmt.Fprintf(writer, "Skipping compatible image %q: %s\n", shortPath, err); err != nil {
|
||||
|
@ -150,6 +150,7 @@ func TestGetCompatibleImageMeasurements(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
csp := cloudprovider.Azure
|
||||
attestationVariant := variant.AzureSEVSNP{}
|
||||
zero := versionsapi.Version{
|
||||
Ref: "-",
|
||||
Stream: "stable",
|
||||
@ -204,7 +205,7 @@ func TestGetCompatibleImageMeasurements(t *testing.T) {
|
||||
|
||||
pubK := []byte("-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEu78QgxOOcao6U91CSzEXxrKhvFTt\nJHNy+eX6EMePtDm8CnDF9HSwnTlD0itGJ/XHPQA5YX10fJAqI1y+ehlFMw==\n-----END PUBLIC KEY-----")
|
||||
|
||||
upgrades, err := getCompatibleImageMeasurements(context.Background(), &bytes.Buffer{}, client, singleUUIDVerifier(), pubK, csp, images, logger.NewTest(t))
|
||||
upgrades, err := getCompatibleImageMeasurements(context.Background(), &bytes.Buffer{}, client, singleUUIDVerifier(), pubK, csp, attestationVariant, images, logger.NewTest(t))
|
||||
assert.NoError(err)
|
||||
|
||||
for _, measurement := range upgrades {
|
||||
@ -297,7 +298,7 @@ type stubVersionCollector struct {
|
||||
someErr error
|
||||
}
|
||||
|
||||
func (s *stubVersionCollector) newMeasurements(_ context.Context, _ cloudprovider.Provider, _ []versionsapi.Version) (map[string]measurements.M, error) {
|
||||
func (s *stubVersionCollector) newMeasurements(_ context.Context, _ cloudprovider.Provider, _ variant.Variant, _ []versionsapi.Version) (map[string]measurements.M, error) {
|
||||
return s.supportedImageVersions, nil
|
||||
}
|
||||
|
||||
|
@ -15,9 +15,9 @@ go_library(
|
||||
"//internal/cloud/cloudprovider",
|
||||
"//internal/constants",
|
||||
"//internal/logger",
|
||||
"//internal/variant",
|
||||
"//internal/versionsapi",
|
||||
"//internal/versionsapi/fetcher",
|
||||
"@in_gopkg_yaml_v3//:yaml_v3",
|
||||
"@sh_helm_helm_v3//pkg/action",
|
||||
"@sh_helm_helm_v3//pkg/cli",
|
||||
],
|
||||
|
@ -10,17 +10,12 @@ package upgrade
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/v2/internal/variant"
|
||||
"github.com/edgelesssys/constellation/v2/internal/versionsapi"
|
||||
"github.com/edgelesssys/constellation/v2/internal/versionsapi/fetcher"
|
||||
)
|
||||
@ -31,7 +26,9 @@ type upgradeInfo struct {
|
||||
imageRef string
|
||||
}
|
||||
|
||||
func fetchUpgradeInfo(ctx context.Context, csp cloudprovider.Provider, toImage string) (upgradeInfo, error) {
|
||||
func fetchUpgradeInfo(ctx context.Context, csp cloudprovider.Provider,
|
||||
attestationVariant variant.Variant, toImage string,
|
||||
) (upgradeInfo, error) {
|
||||
info := upgradeInfo{
|
||||
measurements: make(measurements.M),
|
||||
shortPath: toImage,
|
||||
@ -43,20 +40,17 @@ func fetchUpgradeInfo(ctx context.Context, csp cloudprovider.Provider, toImage s
|
||||
return upgradeInfo{}, err
|
||||
}
|
||||
|
||||
measurementsURL, _, err := versionsapi.MeasurementURL(ver, csp)
|
||||
measurementsURL, _, err := versionsapi.MeasurementURL(ver)
|
||||
if err != nil {
|
||||
return upgradeInfo{}, err
|
||||
}
|
||||
|
||||
fetchedMeasurements, err := fetchMeasurements(
|
||||
fetchedMeasurements := measurements.M{}
|
||||
if err := fetchedMeasurements.FetchNoVerify(
|
||||
ctx, http.DefaultClient,
|
||||
measurementsURL,
|
||||
measurements.WithMetadata{
|
||||
CSP: csp,
|
||||
Image: toImage,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
ver, csp, attestationVariant,
|
||||
); err != nil {
|
||||
return upgradeInfo{}, err
|
||||
}
|
||||
info.measurements = fetchedMeasurements
|
||||
@ -74,56 +68,6 @@ func fetchUpgradeInfo(ctx context.Context, csp cloudprovider.Provider, toImage s
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// fetchMeasurements is essentially a copy of measurements.FetchAndVerify, but with verification removed.
|
||||
// This is necessary since the e2e tests may target release images for which the measurements are signed with the release public key.
|
||||
// It is easier to skip verification than to implement a second bazel target with the enterprise build tag set.
|
||||
func fetchMeasurements(ctx context.Context, client *http.Client, measurementsURL *url.URL, metadata measurements.WithMetadata) (measurements.M, error) {
|
||||
measurementsRaw, err := getFromURL(ctx, client, measurementsURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch measurements: %w", err)
|
||||
}
|
||||
|
||||
var mWithMetadata measurements.WithMetadata
|
||||
if err := json.Unmarshal(measurementsRaw, &mWithMetadata); err != nil {
|
||||
if yamlErr := yaml.Unmarshal(measurementsRaw, &mWithMetadata); yamlErr != nil {
|
||||
return nil, errors.Join(
|
||||
err,
|
||||
fmt.Errorf("trying yaml format: %w", yamlErr),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if mWithMetadata.CSP != metadata.CSP {
|
||||
return nil, fmt.Errorf("invalid measurement metadata: CSP mismatch: expected %s, got %s", metadata.CSP, mWithMetadata.CSP)
|
||||
}
|
||||
if mWithMetadata.Image != metadata.Image {
|
||||
return nil, fmt.Errorf("invalid measurement metadata: image mismatch: expected %s, got %s", metadata.Image, mWithMetadata.Image)
|
||||
}
|
||||
|
||||
return mWithMetadata.Measurements, nil
|
||||
}
|
||||
|
||||
func getFromURL(ctx context.Context, client *http.Client, sourceURL *url.URL) ([]byte, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, sourceURL.String(), http.NoBody)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return []byte{}, fmt.Errorf("http status code: %d", resp.StatusCode)
|
||||
}
|
||||
content, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
return content, nil
|
||||
}
|
||||
|
||||
func fetchImageRef(ctx context.Context, client *fetcher.Fetcher, csp cloudprovider.Provider, imageInfo versionsapi.ImageInfo) (string, error) {
|
||||
imageInfo, err := client.FetchImageInfo(ctx, imageInfo)
|
||||
if err != nil {
|
||||
|
@ -240,7 +240,12 @@ func writeUpgradeConfig(require *require.Assertions, image string, kubernetes st
|
||||
}
|
||||
require.NoError(err, longMsg)
|
||||
|
||||
info, err := fetchUpgradeInfo(context.Background(), cfg.GetProvider(), image)
|
||||
info, err := fetchUpgradeInfo(
|
||||
context.Background(),
|
||||
cfg.GetProvider(),
|
||||
cfg.GetAttestationConfig().GetVariant(),
|
||||
image,
|
||||
)
|
||||
require.NoError(err)
|
||||
|
||||
log.Printf("Setting image version: %s\n", info.shortPath)
|
||||
|
@ -16,6 +16,7 @@ go_library(
|
||||
"//internal/cloud/cloudprovider",
|
||||
"//internal/sigstore",
|
||||
"//internal/variant",
|
||||
"//internal/versionsapi",
|
||||
"@com_github_google_go_tpm//tpmutil",
|
||||
"@com_github_siderolabs_talos_pkg_machinery//config/encoder",
|
||||
"@in_gopkg_yaml_v3//:yaml_v3",
|
||||
@ -28,6 +29,8 @@ go_test(
|
||||
embed = [":measurements"],
|
||||
deps = [
|
||||
"//internal/cloud/cloudprovider",
|
||||
"//internal/variant",
|
||||
"//internal/versionsapi",
|
||||
"@com_github_siderolabs_talos_pkg_machinery//config/encoder",
|
||||
"@com_github_stretchr_testify//assert",
|
||||
"@com_github_stretchr_testify//require",
|
||||
|
@ -82,7 +82,7 @@ func main() {
|
||||
log.Println("Found", variant)
|
||||
returnStmtCtr++
|
||||
// retrieve and validate measurements for the given CSP and image
|
||||
measuremnts := mustGetMeasurements(ctx, rekor, []byte(constants.CosignPublicKey), http.DefaultClient, provider, defaultConf.Image)
|
||||
measuremnts := mustGetMeasurements(ctx, rekor, []byte(constants.CosignPublicKey), http.DefaultClient, provider, variant, defaultConf.Image)
|
||||
// replace the return statement with a composite literal containing the validated measurements
|
||||
clause.Values[0] = measurementsCompositeLiteral(measuremnts)
|
||||
}
|
||||
@ -107,12 +107,17 @@ func main() {
|
||||
}
|
||||
|
||||
// mustGetMeasurements fetches the measurements for the given image and CSP and verifies them.
|
||||
func mustGetMeasurements(ctx context.Context, verifier rekorVerifier, cosignPublicKey []byte, client *http.Client, provider cloudprovider.Provider, image string) measurements.M {
|
||||
measurementsURL, err := measurementURL(provider, image, "measurements.json")
|
||||
func mustGetMeasurements(ctx context.Context, verifier rekorVerifier, cosignPublicKey []byte, client *http.Client, provider cloudprovider.Provider, attestationVariant variant.Variant, image string) measurements.M {
|
||||
measurementsURL, err := measurementURL(image, "measurements.json")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
signatureURL, err := measurementURL(provider, image, "measurements.json.sig")
|
||||
signatureURL, err := measurementURL(image, "measurements.json.sig")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
imageVersion, err := versionsapi.NewVersionFromShortPath(image, versionsapi.VersionKindImage)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -124,10 +129,9 @@ func mustGetMeasurements(ctx context.Context, verifier rekorVerifier, cosignPubl
|
||||
measurementsURL,
|
||||
signatureURL,
|
||||
cosignPublicKey,
|
||||
measurements.WithMetadata{
|
||||
CSP: provider,
|
||||
Image: image,
|
||||
},
|
||||
imageVersion,
|
||||
provider,
|
||||
attestationVariant,
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@ -139,14 +143,14 @@ func mustGetMeasurements(ctx context.Context, verifier rekorVerifier, cosignPubl
|
||||
}
|
||||
|
||||
// measurementURL returns the URL for the measurements file for the given image and CSP.
|
||||
func measurementURL(provider cloudprovider.Provider, image, file string) (*url.URL, error) {
|
||||
func measurementURL(image, file string) (*url.URL, error) {
|
||||
version, err := versionsapi.NewVersionFromShortPath(image, versionsapi.VersionKindImage)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing image name: %w", err)
|
||||
}
|
||||
|
||||
return url.Parse(
|
||||
version.ArtifactsURL() + path.Join("/image", "csp", strings.ToLower(provider.String()), file),
|
||||
version.ArtifactsURL("v2") + path.Join("/image", file),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -28,12 +28,14 @@ import (
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/v2/internal/sigstore"
|
||||
"github.com/edgelesssys/constellation/v2/internal/variant"
|
||||
"github.com/google/go-tpm/tpmutil"
|
||||
"github.com/siderolabs/talos/pkg/machinery/config/encoder"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/v2/internal/sigstore"
|
||||
"github.com/edgelesssys/constellation/v2/internal/variant"
|
||||
"github.com/edgelesssys/constellation/v2/internal/versionsapi"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -67,6 +69,22 @@ type WithMetadata struct {
|
||||
Measurements M `json:"measurements" yaml:"measurements"`
|
||||
}
|
||||
|
||||
// ImageMeasurementsV2 is a struct to hold measurements for a specific image.
|
||||
// .List contains measurements for all variants of the image.
|
||||
type ImageMeasurementsV2 struct {
|
||||
Version string `json:"version" yaml:"version"`
|
||||
Ref string `json:"ref" yaml:"ref"`
|
||||
Stream string `json:"stream" yaml:"stream"`
|
||||
List []ImageMeasurementsV2Entry `json:"list" yaml:"list"`
|
||||
}
|
||||
|
||||
// ImageMeasurementsV2Entry is a struct to hold measurements for one variant of a specific image.
|
||||
type ImageMeasurementsV2Entry struct {
|
||||
CSP cloudprovider.Provider `json:"csp" yaml:"csp"`
|
||||
AttestationVariant string `json:"attestationVariant" yaml:"attestationVariant"`
|
||||
Measurements M `json:"measurements" yaml:"measurements"`
|
||||
}
|
||||
|
||||
// MarshalYAML returns the YAML encoding of m.
|
||||
func (m M) MarshalYAML() (any, error) {
|
||||
// cast to prevent infinite recursion
|
||||
@ -86,9 +104,9 @@ func (m M) MarshalYAML() (any, error) {
|
||||
// The hash of the fetched measurements is returned.
|
||||
func (m *M) FetchAndVerify(
|
||||
ctx context.Context, client *http.Client, measurementsURL, signatureURL *url.URL,
|
||||
publicKey []byte, metadata WithMetadata,
|
||||
publicKey []byte, version versionsapi.Version, csp cloudprovider.Provider, attestationVariant variant.Variant,
|
||||
) (string, error) {
|
||||
measurements, err := getFromURL(ctx, client, measurementsURL)
|
||||
measurementsRaw, err := getFromURL(ctx, client, measurementsURL)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to fetch measurements: %w", err)
|
||||
}
|
||||
@ -96,34 +114,38 @@ func (m *M) FetchAndVerify(
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to fetch signature: %w", err)
|
||||
}
|
||||
if err := sigstore.VerifySignature(measurements, signature, publicKey); err != nil {
|
||||
if err := sigstore.VerifySignature(measurementsRaw, signature, publicKey); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var mWithMetadata WithMetadata
|
||||
if err := json.Unmarshal(measurements, &mWithMetadata); err != nil {
|
||||
if yamlErr := yaml.Unmarshal(measurements, &mWithMetadata); yamlErr != nil {
|
||||
return "", errors.Join(
|
||||
err,
|
||||
fmt.Errorf("trying yaml format: %w", yamlErr),
|
||||
)
|
||||
}
|
||||
var measurements ImageMeasurementsV2
|
||||
if err := json.Unmarshal(measurementsRaw, &measurements); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if mWithMetadata.CSP != metadata.CSP {
|
||||
return "", fmt.Errorf("invalid measurement metadata: CSP mismatch: expected %s, got %s", metadata.CSP, mWithMetadata.CSP)
|
||||
if err := m.fromImageMeasurementsV2(measurements, version, csp, attestationVariant); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if mWithMetadata.Image != metadata.Image {
|
||||
return "", fmt.Errorf("invalid measurement metadata: image mismatch: expected %s, got %s", metadata.Image, mWithMetadata.Image)
|
||||
}
|
||||
|
||||
*m = mWithMetadata.Measurements
|
||||
|
||||
shaHash := sha256.Sum256(measurements)
|
||||
|
||||
shaHash := sha256.Sum256(measurementsRaw)
|
||||
return hex.EncodeToString(shaHash[:]), nil
|
||||
}
|
||||
|
||||
// FetchNoVerify fetches measurement via provided URLs,
|
||||
// using client for download. Measurements are not verified.
|
||||
func (m *M) FetchNoVerify(ctx context.Context, client *http.Client, measurementsURL *url.URL,
|
||||
version versionsapi.Version, csp cloudprovider.Provider, attestationVariant variant.Variant,
|
||||
) error {
|
||||
measurementsRaw, err := getFromURL(ctx, client, measurementsURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to fetch measurements: %w", err)
|
||||
}
|
||||
|
||||
var measurements ImageMeasurementsV2
|
||||
if err := json.Unmarshal(measurementsRaw, &measurements); err != nil {
|
||||
return err
|
||||
}
|
||||
return m.fromImageMeasurementsV2(measurements, version, csp, attestationVariant)
|
||||
}
|
||||
|
||||
// CopyFrom copies over all values from other. Overwriting existing values,
|
||||
// but keeping not specified values untouched.
|
||||
func (m *M) CopyFrom(other M) {
|
||||
@ -232,6 +254,51 @@ func (m *M) UnmarshalYAML(unmarshal func(any) error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *M) fromImageMeasurementsV2(
|
||||
measurements ImageMeasurementsV2, wantVersion versionsapi.Version,
|
||||
csp cloudprovider.Provider, attestationVariant variant.Variant,
|
||||
) error {
|
||||
gotVersion := versionsapi.Version{
|
||||
Ref: measurements.Ref,
|
||||
Stream: measurements.Stream,
|
||||
Version: measurements.Version,
|
||||
Kind: versionsapi.VersionKindImage,
|
||||
}
|
||||
if !wantVersion.Equal(gotVersion) {
|
||||
return fmt.Errorf("invalid measurement metadata: version mismatch: expected %s, got %s", wantVersion.ShortPath(), gotVersion.ShortPath())
|
||||
}
|
||||
|
||||
// find measurements for requested image in list
|
||||
var measurementsEntry ImageMeasurementsV2Entry
|
||||
var found bool
|
||||
for _, entry := range measurements.List {
|
||||
gotCSP := entry.CSP
|
||||
if gotCSP != csp {
|
||||
continue
|
||||
}
|
||||
gotAttestationVariant, err := variant.FromString(entry.AttestationVariant)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if gotAttestationVariant == nil || attestationVariant == nil {
|
||||
continue
|
||||
}
|
||||
if !gotAttestationVariant.Equal(attestationVariant) {
|
||||
continue
|
||||
}
|
||||
measurementsEntry = entry
|
||||
found = true
|
||||
break
|
||||
}
|
||||
|
||||
if !found {
|
||||
return fmt.Errorf("invalid measurement metadata: no measurements found for csp %s, attestationVariant %s and image %s", csp.String(), attestationVariant, wantVersion.ShortPath())
|
||||
}
|
||||
|
||||
*m = measurementsEntry.Measurements
|
||||
return nil
|
||||
}
|
||||
|
||||
// Measurement wraps expected PCR value and whether it is enforced.
|
||||
type Measurement struct {
|
||||
// Expected measurement value.
|
||||
|
@ -15,11 +15,14 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||
"github.com/siderolabs/talos/pkg/machinery/config/encoder"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/v2/internal/variant"
|
||||
"github.com/edgelesssys/constellation/v2/internal/versionsapi"
|
||||
)
|
||||
|
||||
func TestMarshal(t *testing.T) {
|
||||
@ -354,7 +357,9 @@ func TestMeasurementsFetchAndVerify(t *testing.T) {
|
||||
|
||||
testCases := map[string]struct {
|
||||
measurements string
|
||||
metadata WithMetadata
|
||||
csp cloudprovider.Provider
|
||||
attestationVariant variant.Variant
|
||||
imageVersion versionsapi.Version
|
||||
measurementsStatus int
|
||||
signature string
|
||||
signatureStatus int
|
||||
@ -363,70 +368,66 @@ func TestMeasurementsFetchAndVerify(t *testing.T) {
|
||||
wantError bool
|
||||
}{
|
||||
"json measurements": {
|
||||
measurements: `{"csp":"test","image":"test","measurements":{"0":{"expected":"0000000000000000000000000000000000000000000000000000000000000000","warnOnly":false}}}`,
|
||||
metadata: WithMetadata{CSP: cloudprovider.Unknown, Image: "test"},
|
||||
measurements: `{"version":"v1.0.0-test","ref":"-","stream":"stable","list":[{"csp":"Unknown","attestationVariant":"dummy","measurements":{"0":{"expected":"0000000000000000000000000000000000000000000000000000000000000000","warnOnly":false}}}]}`,
|
||||
csp: cloudprovider.Unknown,
|
||||
imageVersion: versionsapi.Version{Ref: "-", Stream: "stable", Version: "v1.0.0-test", Kind: versionsapi.VersionKindImage},
|
||||
measurementsStatus: http.StatusOK,
|
||||
signature: "MEYCIQD1RR91pWPw1BMWXTSmTBHg/JtfKerbZNQ9PJTWDdW0sgIhANQbETJGb67qzQmMVmcq007VUFbHRMtYWKZeeyRf0gVa",
|
||||
signature: "MEUCIHuW2420EqN4Kj6OEaVMmufH7d01vyR1J+SWg8H4elyBAiEA1Ki5Hfq0iI70qpViYbrTFrd8e840NjtdAxGqJKiJgbA=",
|
||||
signatureStatus: http.StatusOK,
|
||||
wantMeasurements: M{
|
||||
0: WithAllBytes(0x00, Enforce, PCRMeasurementLength),
|
||||
},
|
||||
wantSHA: "c04e13c1312b6f5659303871d14bf49b05c99a6515548763b6322f60bbb61a24",
|
||||
},
|
||||
"yaml measurements": {
|
||||
measurements: "csp: test\nimage: test\nmeasurements:\n 0:\n expected: \"0000000000000000000000000000000000000000000000000000000000000000\"\n warnOnly: false\n",
|
||||
metadata: WithMetadata{CSP: cloudprovider.Unknown, Image: "test"},
|
||||
measurementsStatus: http.StatusOK,
|
||||
signature: "MEUCIQC9WI2ijlQjBktYFctKpbnqkUTey3U9W99Jp1NTLi5AbQIgNZxxOtiawgTkWPXLoH9D2CxpEjxQrqLn/zWF6NoKxWQ=",
|
||||
signatureStatus: http.StatusOK,
|
||||
wantMeasurements: M{
|
||||
0: WithAllBytes(0x00, Enforce, PCRMeasurementLength),
|
||||
},
|
||||
wantSHA: "648fcfd5d22e623a948ab2dd4eb334be2701d8f158231726084323003daab8d4",
|
||||
wantSHA: "7269a1e8c6a379b86af605f993352df1d4a289bbf79fe655fd78338bd7549d52",
|
||||
},
|
||||
"404 measurements": {
|
||||
measurements: `{"csp":"test","image":"test","measurements":{"0":{"expected":"0000000000000000000000000000000000000000000000000000000000000000","warnOnly":false}}}`,
|
||||
metadata: WithMetadata{CSP: cloudprovider.Unknown, Image: "test"},
|
||||
measurements: `{"version":"v1.0.0-test","ref":"-","stream":"stable","list":[{"csp":"Unknown","attestationVariant":"dummy","measurements":{"0":{"expected":"0000000000000000000000000000000000000000000000000000000000000000","warnOnly":false}}}]}`,
|
||||
csp: cloudprovider.Unknown,
|
||||
imageVersion: versionsapi.Version{Ref: "-", Stream: "stable", Version: "v1.0.0-test", Kind: versionsapi.VersionKindImage},
|
||||
measurementsStatus: http.StatusNotFound,
|
||||
signature: "MEYCIQD1RR91pWPw1BMWXTSmTBHg/JtfKerbZNQ9PJTWDdW0sgIhANQbETJGb67qzQmMVmcq007VUFbHRMtYWKZeeyRf0gVa",
|
||||
signature: "MEUCIHuW2420EqN4Kj6OEaVMmufH7d01vyR1J+SWg8H4elyBAiEA1Ki5Hfq0iI70qpViYbrTFrd8e840NjtdAxGqJKiJgbA=",
|
||||
signatureStatus: http.StatusOK,
|
||||
wantError: true,
|
||||
},
|
||||
"404 signature": {
|
||||
measurements: `{"csp":"test","image":"test","measurements":{"0":{"expected":"0000000000000000000000000000000000000000000000000000000000000000","warnOnly":false}}}`,
|
||||
metadata: WithMetadata{CSP: cloudprovider.Unknown, Image: "test"},
|
||||
measurements: `{"version":"v1.0.0-test","ref":"-","stream":"stable","list":[{"csp":"Unknown","attestationVariant":"dummy","measurements":{"0":{"expected":"0000000000000000000000000000000000000000000000000000000000000000","warnOnly":false}}}]}`,
|
||||
csp: cloudprovider.Unknown,
|
||||
imageVersion: versionsapi.Version{Ref: "-", Stream: "stable", Version: "v1.0.0-test", Kind: versionsapi.VersionKindImage},
|
||||
measurementsStatus: http.StatusOK,
|
||||
signature: "MEYCIQD1RR91pWPw1BMWXTSmTBHg/JtfKerbZNQ9PJTWDdW0sgIhANQbETJGb67qzQmMVmcq007VUFbHRMtYWKZeeyRf0gVa",
|
||||
signature: "MEUCIHuW2420EqN4Kj6OEaVMmufH7d01vyR1J+SWg8H4elyBAiEA1Ki5Hfq0iI70qpViYbrTFrd8e840NjtdAxGqJKiJgbA=",
|
||||
signatureStatus: http.StatusNotFound,
|
||||
wantError: true,
|
||||
},
|
||||
"broken signature": {
|
||||
measurements: `{"csp":"test","image":"test","measurements":{"0":{"expected":"0000000000000000000000000000000000000000000000000000000000000000","warnOnly":false}}}`,
|
||||
metadata: WithMetadata{CSP: cloudprovider.Unknown, Image: "test"},
|
||||
measurements: `{"version":"v1.0.0-test","ref":"-","stream":"stable","list":[{"csp":"Unknown","attestationVariant":"dummy","measurements":{"0":{"expected":"0000000000000000000000000000000000000000000000000000000000000000","warnOnly":false}}}]}`,
|
||||
csp: cloudprovider.Unknown,
|
||||
imageVersion: versionsapi.Version{Ref: "-", Stream: "stable", Version: "v1.0.0-test", Kind: versionsapi.VersionKindImage},
|
||||
measurementsStatus: http.StatusOK,
|
||||
signature: "AAAAAAA1RR91pWPw1BMWXTSmTBHg/JtfKerbZNQ9PJTWDdW0sgIhANQbETJGb67qzQmMVmcq007VUFbHRMtYWKZeeyRf0gVa",
|
||||
signatureStatus: http.StatusOK,
|
||||
wantError: true,
|
||||
},
|
||||
"metadata CSP mismatch": {
|
||||
measurements: `{"csp":"test","image":"test","measurements":{"0":{"expected":"0000000000000000000000000000000000000000000000000000000000000000","warnOnly":false}}}`,
|
||||
metadata: WithMetadata{CSP: cloudprovider.GCP, Image: "test"},
|
||||
measurements: `{"version":"v1.0.0-test","ref":"-","stream":"stable","list":[{"csp":"Unknown","attestationVariant":"dummy","measurements":{"0":{"expected":"0000000000000000000000000000000000000000000000000000000000000000","warnOnly":false}}}]}`,
|
||||
csp: cloudprovider.GCP,
|
||||
imageVersion: versionsapi.Version{Ref: "-", Stream: "stable", Version: "v1.0.0-test", Kind: versionsapi.VersionKindImage},
|
||||
measurementsStatus: http.StatusOK,
|
||||
signature: "MEYCIQD1RR91pWPw1BMWXTSmTBHg/JtfKerbZNQ9PJTWDdW0sgIhANQbETJGb67qzQmMVmcq007VUFbHRMtYWKZeeyRf0gVa",
|
||||
signature: "MEUCIHuW2420EqN4Kj6OEaVMmufH7d01vyR1J+SWg8H4elyBAiEA1Ki5Hfq0iI70qpViYbrTFrd8e840NjtdAxGqJKiJgbA=",
|
||||
signatureStatus: http.StatusOK,
|
||||
wantError: true,
|
||||
},
|
||||
"metadata image mismatch": {
|
||||
measurements: `{"csp":"test","image":"test","measurements":{"0":{"expected":"0000000000000000000000000000000000000000000000000000000000000000","warnOnly":false}}}`,
|
||||
metadata: WithMetadata{CSP: cloudprovider.Unknown, Image: "another-image"},
|
||||
measurements: `{"version":"v1.0.0-test","ref":"-","stream":"stable","list":[{"csp":"Unknown","attestationVariant":"dummy","measurements":{"0":{"expected":"0000000000000000000000000000000000000000000000000000000000000000","warnOnly":false}}}]}`,
|
||||
csp: cloudprovider.Unknown,
|
||||
imageVersion: versionsapi.Version{Ref: "-", Stream: "stable", Version: "v1.0.0-another-image", Kind: versionsapi.VersionKindImage},
|
||||
measurementsStatus: http.StatusOK,
|
||||
signature: "MEYCIQD1RR91pWPw1BMWXTSmTBHg/JtfKerbZNQ9PJTWDdW0sgIhANQbETJGb67qzQmMVmcq007VUFbHRMtYWKZeeyRf0gVa",
|
||||
signature: "MEUCIHuW2420EqN4Kj6OEaVMmufH7d01vyR1J+SWg8H4elyBAiEA1Ki5Hfq0iI70qpViYbrTFrd8e840NjtdAxGqJKiJgbA=",
|
||||
signatureStatus: http.StatusOK,
|
||||
wantError: true,
|
||||
},
|
||||
"not yaml or json": {
|
||||
"not json": {
|
||||
measurements: "This is some content to be signed!\n",
|
||||
metadata: WithMetadata{CSP: cloudprovider.Unknown, Image: "test"},
|
||||
csp: cloudprovider.Unknown,
|
||||
imageVersion: versionsapi.Version{Ref: "-", Stream: "stable", Version: "v1.0.0-test", Kind: versionsapi.VersionKindImage},
|
||||
measurementsStatus: http.StatusOK,
|
||||
signature: "MEUCIQCGA/lSu5qCJgNNvgMaTKJ9rj6vQMecUDaQo3ukaiAfUgIgWoxXRoDKLY9naN7YgxokM7r2fwnyYk3M2WKJJO1g6yo=",
|
||||
signatureStatus: http.StatusOK,
|
||||
@ -441,6 +442,10 @@ func TestMeasurementsFetchAndVerify(t *testing.T) {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
if tc.attestationVariant == nil {
|
||||
tc.attestationVariant = variant.Dummy{}
|
||||
}
|
||||
|
||||
client := newTestClient(func(req *http.Request) *http.Response {
|
||||
if req.URL.String() == measurementsURL.String() {
|
||||
return &http.Response{
|
||||
@ -469,7 +474,9 @@ func TestMeasurementsFetchAndVerify(t *testing.T) {
|
||||
context.Background(), client,
|
||||
measurementsURL, signatureURL,
|
||||
cosignPublicKey,
|
||||
tc.metadata,
|
||||
tc.imageVersion,
|
||||
tc.csp,
|
||||
tc.attestationVariant,
|
||||
)
|
||||
|
||||
if tc.wantError {
|
||||
|
@ -185,8 +185,12 @@ const (
|
||||
|
||||
// CDNRepositoryURL is the base URL of the Constellation CDN artifact repository.
|
||||
CDNRepositoryURL = "https://cdn.confidential.cloud"
|
||||
// CDNAPIPrefix is the prefix of the Constellation API.
|
||||
CDNAPIPrefix = "constellation/v1"
|
||||
// CDNAPIBase is the (un-versioned) prefix of the Constellation API.
|
||||
CDNAPIBase = "constellation"
|
||||
// CDNAPIPrefix is the prefix of the Constellation API (V1).
|
||||
CDNAPIPrefix = CDNAPIBase + "/v1"
|
||||
// CDNAPIPrefixV2 is the prefix of the Constellation API (v2).
|
||||
CDNAPIPrefixV2 = CDNAPIBase + "/v2"
|
||||
// CDNMeasurementsFile is name of file containing image measurements.
|
||||
CDNMeasurementsFile = "measurements.json"
|
||||
// CDNMeasurementsSignature is name of file containing signature for CDNMeasurementsFile.
|
||||
|
@ -47,7 +47,7 @@ func New(ctx context.Context, region, bucket string, log *logger.Logger) (*Archi
|
||||
|
||||
// Archive reads the OS image in img and uploads it as key.
|
||||
func (a *Archivist) Archive(ctx context.Context, version versionsapi.Version, csp, variant string, img io.Reader) (string, error) {
|
||||
key, err := url.JoinPath(version.ArtifactPath(), version.Kind.String(), "csp", csp, variant, "image.raw")
|
||||
key, err := url.JoinPath(version.ArtifactPath("v1"), version.Kind.String(), "csp", csp, variant, "image.raw")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -14,7 +14,6 @@ go_library(
|
||||
importpath = "github.com/edgelesssys/constellation/v2/internal/versionsapi",
|
||||
visibility = ["//:__subpackages__"],
|
||||
deps = [
|
||||
"//internal/cloud/cloudprovider",
|
||||
"//internal/constants",
|
||||
"@org_golang_x_mod//semver",
|
||||
],
|
||||
|
@ -185,8 +185,8 @@ func (c *Client) DeleteVersion(ctx context.Context, ver versionsapi.Version) err
|
||||
retErr = errors.Join(retErr, fmt.Errorf("updating latest version: %w", err))
|
||||
}
|
||||
|
||||
c.log.Debugf("Deleting artifact path %s for %s", ver.ArtifactPath(), ver.Version)
|
||||
if err := c.deletePath(ctx, ver.ArtifactPath()); err != nil {
|
||||
c.log.Debugf("Deleting artifact path %s for %s", ver.ArtifactPath("v1"), ver.Version)
|
||||
if err := c.deletePath(ctx, ver.ArtifactPath("v1")); err != nil {
|
||||
retErr = errors.Join(retErr, fmt.Errorf("deleting artifact path: %w", err))
|
||||
}
|
||||
|
||||
|
@ -15,9 +15,9 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||
"golang.org/x/mod/semver"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||
)
|
||||
|
||||
// ReleaseRef is the ref used for release versions.
|
||||
@ -82,6 +82,14 @@ func (v Version) Validate() error {
|
||||
return retErr
|
||||
}
|
||||
|
||||
// Equal returns true if the versions are equal.
|
||||
func (v Version) Equal(other Version) bool {
|
||||
return v.Ref == other.Ref &&
|
||||
v.Stream == other.Stream &&
|
||||
v.Version == other.Version &&
|
||||
v.Kind == other.Kind
|
||||
}
|
||||
|
||||
// Major returns the major version corresponding to the version.
|
||||
// For example, if the version is "v1.2.3", the major version is "v1".
|
||||
func (v Version) Major() string {
|
||||
@ -146,15 +154,16 @@ func (v Version) ListPath(gran Granularity) string {
|
||||
|
||||
// ArtifactsURL returns the URL to the artifacts stored for this version.
|
||||
// The URL points to a directory.
|
||||
func (v Version) ArtifactsURL() string {
|
||||
return constants.CDNRepositoryURL + "/" + v.ArtifactPath()
|
||||
func (v Version) ArtifactsURL(apiVersion string) string {
|
||||
return constants.CDNRepositoryURL + "/" + v.ArtifactPath(apiVersion)
|
||||
}
|
||||
|
||||
// ArtifactPath returns the path to the artifacts stored for this version.
|
||||
// The path points to a directory.
|
||||
func (v Version) ArtifactPath() string {
|
||||
func (v Version) ArtifactPath(apiVersion string) string {
|
||||
return path.Join(
|
||||
constants.CDNAPIPrefix,
|
||||
constants.CDNAPIBase,
|
||||
apiVersion,
|
||||
"ref", v.Ref,
|
||||
"stream", v.Stream,
|
||||
v.Version,
|
||||
@ -325,17 +334,18 @@ func ValidateStream(ref, stream string) error {
|
||||
return fmt.Errorf("stream %q is unknown or not supported on ref %q", stream, ref)
|
||||
}
|
||||
|
||||
// MeasurementURL builds the measurement and signature URLs for the given version and CSP.
|
||||
func MeasurementURL(version Version, csp cloudprovider.Provider) (measurementURL, signatureURL *url.URL, err error) {
|
||||
// MeasurementURL builds the measurement and signature URLs for the given version.
|
||||
func MeasurementURL(version Version) (measurementURL, signatureURL *url.URL, err error) {
|
||||
const apiVersion = "v2"
|
||||
if version.Kind != VersionKindImage {
|
||||
return &url.URL{}, &url.URL{}, fmt.Errorf("kind %q is not supported", version.Kind)
|
||||
}
|
||||
|
||||
measurementPath, err := url.JoinPath(version.ArtifactsURL(), "image", "csp", strings.ToLower(csp.String()), constants.CDNMeasurementsFile)
|
||||
measurementPath, err := url.JoinPath(version.ArtifactsURL(apiVersion), "image", constants.CDNMeasurementsFile)
|
||||
if err != nil {
|
||||
return &url.URL{}, &url.URL{}, fmt.Errorf("joining path for measurement: %w", err)
|
||||
}
|
||||
signaturePath, err := url.JoinPath(version.ArtifactsURL(), "image", "csp", strings.ToLower(csp.String()), constants.CDNMeasurementsSignature)
|
||||
signaturePath, err := url.JoinPath(version.ArtifactsURL(apiVersion), "image", constants.CDNMeasurementsSignature)
|
||||
if err != nil {
|
||||
return &url.URL{}, &url.URL{}, fmt.Errorf("joining path for signature: %w", err)
|
||||
}
|
||||
|
@ -10,9 +10,10 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewVersionFromShortPath(t *testing.T) {
|
||||
@ -468,8 +469,8 @@ func TestVersionArtifactURL(t *testing.T) {
|
||||
Kind: VersionKindImage,
|
||||
},
|
||||
csp: cloudprovider.GCP,
|
||||
wantMeasurementURL: constants.CDNRepositoryURL + "/" + constants.CDNAPIPrefix + "/ref/feat-some-feature/stream/nightly/v2.6.0-pre.0.20230217095603-193dd48ca19f/image/csp/gcp/measurements.json",
|
||||
wantSignatureURL: constants.CDNRepositoryURL + "/" + constants.CDNAPIPrefix + "/ref/feat-some-feature/stream/nightly/v2.6.0-pre.0.20230217095603-193dd48ca19f/image/csp/gcp/measurements.json.sig",
|
||||
wantMeasurementURL: constants.CDNRepositoryURL + "/" + constants.CDNAPIPrefixV2 + "/ref/feat-some-feature/stream/nightly/v2.6.0-pre.0.20230217095603-193dd48ca19f/image/measurements.json",
|
||||
wantSignatureURL: constants.CDNRepositoryURL + "/" + constants.CDNAPIPrefixV2 + "/ref/feat-some-feature/stream/nightly/v2.6.0-pre.0.20230217095603-193dd48ca19f/image/measurements.json.sig",
|
||||
},
|
||||
"fail for wrong kind": {
|
||||
ver: Version{
|
||||
@ -483,7 +484,7 @@ func TestVersionArtifactURL(t *testing.T) {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
measurementURL, signatureURL, err := MeasurementURL(tc.ver, tc.csp)
|
||||
measurementURL, signatureURL, err := MeasurementURL(tc.ver)
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
return
|
||||
@ -560,9 +561,9 @@ func TestVersionArtifactPathURL(t *testing.T) {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
path := tc.ver.ArtifactPath()
|
||||
path := tc.ver.ArtifactPath("v1")
|
||||
assert.Equal(tc.wantPath, path)
|
||||
url := tc.ver.ArtifactsURL()
|
||||
url := tc.ver.ArtifactsURL("v1")
|
||||
assert.Equal(constants.CDNRepositoryURL+"/"+tc.wantPath, url)
|
||||
})
|
||||
}
|
||||
|
@ -182,9 +182,11 @@ The image measurements are a JSON file that contains sets of measurements for th
|
||||
"ref": "<REF>",
|
||||
"stream": "<STREAM>",
|
||||
"list": [
|
||||
"csp": "<CSP>",
|
||||
"attestationVariant": "<ATTESTATION_VARIANT>",
|
||||
"measurements": {"<PCR_INDEX>": {<MEASUREMENT>}}
|
||||
{
|
||||
"csp": "<CSP>",
|
||||
"attestationVariant": "<ATTESTATION_VARIANT>",
|
||||
"measurements": {"<PCR_INDEX>": {<MEASUREMENT>}}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
Loading…
Reference in New Issue
Block a user