api: refactor attestationconfigapi client/fetcher

There is now one SEVSNPVersions type that has a variant
property. That property is used to build the correct JSON
path. The surrounding methods handling the version objects
are also updated to receive a variant argument and work
for multiple variants. This simplifies adding AWS support.
This commit is contained in:
Otto Bittner 2023-11-14 10:03:01 +01:00
parent 5542f9c63c
commit 350397923f
16 changed files with 411 additions and 262 deletions

View file

@ -6,6 +6,8 @@ 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.
Some code in this file (e.g. listing cached files) does not rely on dedicated API objects and instead uses the AWS SDK directly,
for no other reason than original development speed.
*/
package attestationconfigapi
@ -28,25 +30,27 @@ import (
// 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
func reportVersionDir(attestation variant.Variant) string {
return path.Join(attestationURLPath, attestation.String(), cachedVersionsSubDir)
}
// UploadSEVSNPVersionLatest 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,
func (c Client) UploadSEVSNPVersionLatest(ctx context.Context, attestation variant.Variant, inputVersion,
latestAPIVersion SEVSNPVersion, now time.Time, force bool,
) error {
if err := c.cacheAzureSEVSNPVersion(ctx, inputVersion, now); err != nil {
if err := c.cacheSEVSNPVersion(ctx, attestation, inputVersion, now); err != nil {
return fmt.Errorf("reporting version: %w", err)
}
if force {
return c.uploadAzureSEVSNPVersion(ctx, inputVersion, now)
return c.uploadSEVSNPVersion(ctx, attestation, inputVersion, now)
}
versionDates, err := c.listCachedVersions(ctx)
versionDates, err := c.listCachedVersions(ctx, attestation)
if err != nil {
return fmt.Errorf("list reported versions: %w", err)
}
@ -54,7 +58,7 @@ func (c Client) UploadAzureSEVSNPVersionLatest(ctx context.Context, inputVersion
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)
minVersion, minDate, err := c.findMinVersion(ctx, attestation, versionDates)
if err != nil {
return fmt.Errorf("get minimal version: %w", err)
}
@ -72,27 +76,27 @@ func (c Client) UploadAzureSEVSNPVersionLatest(ctx context.Context, inputVersion
if err != nil {
return fmt.Errorf("parsing date: %w", err)
}
if err := c.uploadAzureSEVSNPVersion(ctx, minVersion, t); err != nil {
if err := c.uploadSEVSNPVersion(ctx, attestation, 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 {
// cacheSEVSNPVersion 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) cacheSEVSNPVersion(ctx context.Context, attestation variant.Variant, version SEVSNPVersion, date time.Time) error {
dateStr := date.Format(VersionFormat) + ".json"
res := putCmd{
apiObject: reportedAzureSEVSNPVersionAPI{Version: dateStr, AzureSEVSNPVersion: version},
apiObject: reportedSEVSNPVersionAPI{Version: dateStr, variant: attestation, SEVSNPVersion: version},
signer: c.signer,
}
return res.Execute(ctx, c.s3Client)
}
func (c Client) listCachedVersions(ctx context.Context) ([]string, error) {
func (c Client) listCachedVersions(ctx context.Context, attestation variant.Variant) ([]string, error) {
list, err := c.s3Client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{
Bucket: aws.String(c.bucketID),
Prefix: aws.String(reportVersionDir),
Prefix: aws.String(reportVersionDir(attestation)),
})
if err != nil {
return nil, fmt.Errorf("list objects: %w", err)
@ -108,28 +112,30 @@ func (c Client) listCachedVersions(ctx context.Context) ([]string, error) {
}
// 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
func (c Client) findMinVersion(ctx context.Context, attesation variant.Variant, versionDates []string) (SEVSNPVersion, string, error) {
var minimalVersion *SEVSNPVersion
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"})
obj, err := client.Fetch(ctx, c.s3Client, reportedSEVSNPVersionAPI{Version: date + ".json", variant: attesation})
if err != nil {
return AzureSEVSNPVersion{}, "", fmt.Errorf("get object: %w", err)
return SEVSNPVersion{}, "", fmt.Errorf("get object: %w", err)
}
// Need to set this explicitly as the variant is not part of the marshalled JSON.
obj.variant = attesation
if minimalVersion == nil {
minimalVersion = &obj.AzureSEVSNPVersion
minimalVersion = &obj.SEVSNPVersion
minimalDate = date
} else {
shouldUpdateMinimal, err := isInputNewerThanOtherVersion(*minimalVersion, obj.AzureSEVSNPVersion)
shouldUpdateMinimal, err := isInputNewerThanOtherVersion(*minimalVersion, obj.SEVSNPVersion)
if err != nil {
continue
}
if shouldUpdateMinimal {
minimalVersion = &obj.AzureSEVSNPVersion
minimalVersion = &obj.SEVSNPVersion
minimalDate = date
}
}
@ -138,7 +144,7 @@ func (c Client) findMinVersion(ctx context.Context, versionDates []string) (Azur
}
// isInputNewerThanOtherVersion compares all version fields and returns true if any input field is newer.
func isInputNewerThanOtherVersion(input, other AzureSEVSNPVersion) (bool, error) {
func isInputNewerThanOtherVersion(input, other SEVSNPVersion) (bool, error) {
if input == other {
return false, nil
}
@ -157,19 +163,20 @@ func isInputNewerThanOtherVersion(input, other AzureSEVSNPVersion) (bool, error)
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
// reportedSEVSNPVersionAPI is the request to get the version information of the specific version in the config api.
type reportedSEVSNPVersionAPI struct {
Version string `json:"-"`
variant variant.Variant `json:"-"`
SEVSNPVersion
}
// 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)
func (i reportedSEVSNPVersionAPI) JSONPath() string {
return path.Join(reportVersionDir(i.variant), i.Version)
}
// ValidateRequest validates the request.
func (i reportedAzureSEVSNPVersionAPI) ValidateRequest() error {
func (i reportedSEVSNPVersionAPI) ValidateRequest() error {
if !strings.HasSuffix(i.Version, ".json") {
return fmt.Errorf("version has no .json suffix")
}
@ -177,6 +184,6 @@ func (i reportedAzureSEVSNPVersionAPI) ValidateRequest() error {
}
// Validate is a No-Op at the moment.
func (i reportedAzureSEVSNPVersionAPI) Validate() error {
func (i reportedSEVSNPVersionAPI) Validate() error {
return nil
}