mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-06-19 11:44:20 -04:00
cli: fix Azure SEV-SNP latest version logic (#2343)
This commit is contained in:
parent
2776e40df7
commit
118f789c2f
26 changed files with 547 additions and 245 deletions
182
internal/api/attestationconfigapi/reporter.go
Normal file
182
internal/api/attestationconfigapi/reporter.go
Normal file
|
@ -0,0 +1,182 @@
|
|||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
/*
|
||||
The reporter contains the logic to determine a latest version for Azure SEVSNP based on cached version values observed on CVM instances.
|
||||
*/
|
||||
package attestationconfigapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/api/client"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
|
||||
)
|
||||
|
||||
// cachedVersionsSubDir is the subdirectory in the bucket where the cached versions are stored.
|
||||
const cachedVersionsSubDir = "cached-versions"
|
||||
|
||||
var reportVersionDir = path.Join(attestationURLPath, variant.AzureSEVSNP{}.String(), cachedVersionsSubDir)
|
||||
|
||||
// ErrNoNewerVersion is returned if the input version is not newer than the latest API version.
|
||||
var ErrNoNewerVersion = errors.New("input version is not newer than latest API version")
|
||||
|
||||
// UploadAzureSEVSNPVersionLatest saves the given version to the cache, determines the smallest
|
||||
// TCB version in the cache among the last cacheWindowSize versions and updates
|
||||
// the latest version in the API if there is an update.
|
||||
// force can be used to bypass the validation logic against the cached versions.
|
||||
func (c Client) UploadAzureSEVSNPVersionLatest(ctx context.Context, inputVersion,
|
||||
latestAPIVersion AzureSEVSNPVersion, now time.Time, force bool,
|
||||
) error {
|
||||
if err := c.cacheAzureSEVSNPVersion(ctx, inputVersion, now); err != nil {
|
||||
return fmt.Errorf("reporting version: %w", err)
|
||||
}
|
||||
if force {
|
||||
return c.uploadAzureSEVSNPVersion(ctx, inputVersion, now)
|
||||
}
|
||||
versionDates, err := c.listCachedVersions(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("list reported versions: %w", err)
|
||||
}
|
||||
if len(versionDates) < c.cacheWindowSize {
|
||||
c.s3Client.Logger.Warnf("Skipping version update, found %d, expected %d reported versions.", len(versionDates), c.cacheWindowSize)
|
||||
return nil
|
||||
}
|
||||
minVersion, minDate, err := c.findMinVersion(ctx, versionDates)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get minimal version: %w", err)
|
||||
}
|
||||
c.s3Client.Logger.Infof("Found minimal version: %+v with date: %s", minVersion, minDate)
|
||||
shouldUpdateAPI, err := isInputNewerThanOtherVersion(minVersion, latestAPIVersion)
|
||||
if err != nil {
|
||||
return ErrNoNewerVersion
|
||||
}
|
||||
if !shouldUpdateAPI {
|
||||
c.s3Client.Logger.Infof("Input version: %+v is not newer than latest API version: %+v", minVersion, latestAPIVersion)
|
||||
return nil
|
||||
}
|
||||
c.s3Client.Logger.Infof("Input version: %+v is newer than latest API version: %+v", minVersion, latestAPIVersion)
|
||||
t, err := time.Parse(VersionFormat, minDate)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing date: %w", err)
|
||||
}
|
||||
if err := c.uploadAzureSEVSNPVersion(ctx, minVersion, t); err != nil {
|
||||
return fmt.Errorf("uploading version: %w", err)
|
||||
}
|
||||
c.s3Client.Logger.Infof("Successfully uploaded new Azure SEV-SNP version: %+v", minVersion)
|
||||
return nil
|
||||
}
|
||||
|
||||
// cacheAzureSEVSNPVersion uploads the latest observed version numbers of the Azure SEVSNP. This version is used to later report the latest version numbers to the API.
|
||||
func (c Client) cacheAzureSEVSNPVersion(ctx context.Context, version AzureSEVSNPVersion, date time.Time) error {
|
||||
dateStr := date.Format(VersionFormat) + ".json"
|
||||
res := putCmd{
|
||||
apiObject: reportedAzureSEVSNPVersionAPI{Version: dateStr, AzureSEVSNPVersion: version},
|
||||
signer: c.signer,
|
||||
}
|
||||
return res.Execute(ctx, c.s3Client)
|
||||
}
|
||||
|
||||
func (c Client) listCachedVersions(ctx context.Context) ([]string, error) {
|
||||
list, err := c.s3Client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{
|
||||
Bucket: aws.String(c.bucketID),
|
||||
Prefix: aws.String(reportVersionDir),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("list objects: %w", err)
|
||||
}
|
||||
var dates []string
|
||||
for _, obj := range list.Contents {
|
||||
fileName := path.Base(*obj.Key)
|
||||
if strings.HasSuffix(fileName, ".json") {
|
||||
dates = append(dates, fileName[:len(fileName)-5])
|
||||
}
|
||||
}
|
||||
return dates, nil
|
||||
}
|
||||
|
||||
// findMinVersion finds the minimal version of the given version dates among the latest values in the version window size.
|
||||
func (c Client) findMinVersion(ctx context.Context, versionDates []string) (AzureSEVSNPVersion, string, error) {
|
||||
var minimalVersion *AzureSEVSNPVersion
|
||||
var minimalDate string
|
||||
sort.Sort(sort.Reverse(sort.StringSlice(versionDates))) // sort in reverse order to slice the latest versions
|
||||
versionDates = versionDates[:c.cacheWindowSize]
|
||||
sort.Strings(versionDates) // sort with oldest first to to take the minimal version with the oldest date
|
||||
for _, date := range versionDates {
|
||||
obj, err := client.Fetch(ctx, c.s3Client, reportedAzureSEVSNPVersionAPI{Version: date + ".json"})
|
||||
if err != nil {
|
||||
return AzureSEVSNPVersion{}, "", fmt.Errorf("get object: %w", err)
|
||||
}
|
||||
|
||||
if minimalVersion == nil {
|
||||
minimalVersion = &obj.AzureSEVSNPVersion
|
||||
minimalDate = date
|
||||
} else {
|
||||
shouldUpdateMinimal, err := isInputNewerThanOtherVersion(*minimalVersion, obj.AzureSEVSNPVersion)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if shouldUpdateMinimal {
|
||||
minimalVersion = &obj.AzureSEVSNPVersion
|
||||
minimalDate = date
|
||||
}
|
||||
}
|
||||
}
|
||||
return *minimalVersion, minimalDate, nil
|
||||
}
|
||||
|
||||
// isInputNewerThanOtherVersion compares all version fields and returns true if any input field is newer.
|
||||
func isInputNewerThanOtherVersion(input, other AzureSEVSNPVersion) (bool, error) {
|
||||
if input == other {
|
||||
return false, nil
|
||||
}
|
||||
if input.TEE < other.TEE {
|
||||
return false, fmt.Errorf("input TEE version: %d is older than latest API version: %d", input.TEE, other.TEE)
|
||||
}
|
||||
if input.SNP < other.SNP {
|
||||
return false, fmt.Errorf("input SNP version: %d is older than latest API version: %d", input.SNP, other.SNP)
|
||||
}
|
||||
if input.Microcode < other.Microcode {
|
||||
return false, fmt.Errorf("input Microcode version: %d is older than latest API version: %d", input.Microcode, other.Microcode)
|
||||
}
|
||||
if input.Bootloader < other.Bootloader {
|
||||
return false, fmt.Errorf("input Bootloader version: %d is older than latest API version: %d", input.Bootloader, other.Bootloader)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// reportedAzureSEVSNPVersionAPI is the request to get the version information of the specific version in the config api.
|
||||
type reportedAzureSEVSNPVersionAPI struct {
|
||||
Version string `json:"-"`
|
||||
AzureSEVSNPVersion
|
||||
}
|
||||
|
||||
// JSONPath returns the path to the JSON file for the request to the config api.
|
||||
func (i reportedAzureSEVSNPVersionAPI) JSONPath() string {
|
||||
return path.Join(reportVersionDir, i.Version)
|
||||
}
|
||||
|
||||
// ValidateRequest validates the request.
|
||||
func (i reportedAzureSEVSNPVersionAPI) ValidateRequest() error {
|
||||
if !strings.HasSuffix(i.Version, ".json") {
|
||||
return fmt.Errorf("version has no .json suffix")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate is a No-Op at the moment.
|
||||
func (i reportedAzureSEVSNPVersionAPI) Validate() error {
|
||||
return nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue