cli: image measurements (v2)

This commit is contained in:
Malte Poll 2023-05-22 14:59:28 +02:00 committed by Malte Poll
parent 2ebc0cf2c8
commit e5b394db87
18 changed files with 274 additions and 195 deletions

View file

@ -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
}

View file

@ -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)),

View file

@ -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 {

View file

@ -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
}