2023-09-25 05:53:02 -04:00
/ *
Copyright ( c ) Edgeless Systems GmbH
SPDX - License - Identifier : AGPL - 3.0 - only
* /
/ *
2024-04-16 12:13:47 -04:00
The reporter contains the logic to determine a latest version for SEVSNP based on cached version values observed on CVM instances .
2023-11-14 04:03:01 -05:00
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 .
2023-09-25 05:53:02 -04:00
* /
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"
// 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" )
2023-11-14 04:03:01 -05:00
func reportVersionDir ( attestation variant . Variant ) string {
2023-11-14 07:24:25 -05:00
return path . Join ( AttestationURLPath , attestation . String ( ) , cachedVersionsSubDir )
2023-11-14 04:03:01 -05:00
}
// UploadSEVSNPVersionLatest saves the given version to the cache, determines the smallest
2023-09-25 05:53:02 -04:00
// 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.
2023-11-14 04:03:01 -05:00
func ( c Client ) UploadSEVSNPVersionLatest ( ctx context . Context , attestation variant . Variant , inputVersion ,
latestAPIVersion SEVSNPVersion , now time . Time , force bool ,
2023-09-25 05:53:02 -04:00
) error {
2023-11-14 04:03:01 -05:00
if err := c . cacheSEVSNPVersion ( ctx , attestation , inputVersion , now ) ; err != nil {
2023-09-25 05:53:02 -04:00
return fmt . Errorf ( "reporting version: %w" , err )
}
if force {
2023-11-14 04:03:01 -05:00
return c . uploadSEVSNPVersion ( ctx , attestation , inputVersion , now )
2023-09-25 05:53:02 -04:00
}
2023-11-14 04:03:01 -05:00
versionDates , err := c . listCachedVersions ( ctx , attestation )
2023-09-25 05:53:02 -04:00
if err != nil {
return fmt . Errorf ( "list reported versions: %w" , err )
}
if len ( versionDates ) < c . cacheWindowSize {
2024-02-08 09:20:01 -05:00
c . s3Client . Logger . Warn ( fmt . Sprintf ( "Skipping version update, found %d, expected %d reported versions." , len ( versionDates ) , c . cacheWindowSize ) )
2023-09-25 05:53:02 -04:00
return nil
}
2023-11-14 04:03:01 -05:00
minVersion , minDate , err := c . findMinVersion ( ctx , attestation , versionDates )
2023-09-25 05:53:02 -04:00
if err != nil {
return fmt . Errorf ( "get minimal version: %w" , err )
}
2024-02-08 09:20:01 -05:00
c . s3Client . Logger . Info ( fmt . Sprintf ( "Found minimal version: %+v with date: %s" , minVersion , minDate ) )
2023-09-25 05:53:02 -04:00
shouldUpdateAPI , err := isInputNewerThanOtherVersion ( minVersion , latestAPIVersion )
if err != nil {
return ErrNoNewerVersion
}
if ! shouldUpdateAPI {
2024-02-08 09:20:01 -05:00
c . s3Client . Logger . Info ( fmt . Sprintf ( "Input version: %+v is not newer than latest API version: %+v" , minVersion , latestAPIVersion ) )
2023-09-25 05:53:02 -04:00
return nil
}
2024-02-08 09:20:01 -05:00
c . s3Client . Logger . Info ( fmt . Sprintf ( "Input version: %+v is newer than latest API version: %+v" , minVersion , latestAPIVersion ) )
2023-09-25 05:53:02 -04:00
t , err := time . Parse ( VersionFormat , minDate )
if err != nil {
return fmt . Errorf ( "parsing date: %w" , err )
}
2023-11-14 04:03:01 -05:00
if err := c . uploadSEVSNPVersion ( ctx , attestation , minVersion , t ) ; err != nil {
2023-09-25 05:53:02 -04:00
return fmt . Errorf ( "uploading version: %w" , err )
}
2024-04-16 12:13:47 -04:00
c . s3Client . Logger . Info ( fmt . Sprintf ( "Successfully uploaded new SEV-SNP version: %+v" , minVersion ) )
2023-09-25 05:53:02 -04:00
return nil
}
2024-04-16 12:13:47 -04:00
// cacheSEVSNPVersion uploads the latest observed version numbers of the SEVSNP. This version is used to later report the latest version numbers to the API.
2023-11-14 04:03:01 -05:00
func ( c Client ) cacheSEVSNPVersion ( ctx context . Context , attestation variant . Variant , version SEVSNPVersion , date time . Time ) error {
2023-09-25 05:53:02 -04:00
dateStr := date . Format ( VersionFormat ) + ".json"
res := putCmd {
2023-11-14 04:03:01 -05:00
apiObject : reportedSEVSNPVersionAPI { Version : dateStr , variant : attestation , SEVSNPVersion : version } ,
2023-09-25 05:53:02 -04:00
signer : c . signer ,
}
return res . Execute ( ctx , c . s3Client )
}
2023-11-14 04:03:01 -05:00
func ( c Client ) listCachedVersions ( ctx context . Context , attestation variant . Variant ) ( [ ] string , error ) {
2023-09-25 05:53:02 -04:00
list , err := c . s3Client . ListObjectsV2 ( ctx , & s3 . ListObjectsV2Input {
Bucket : aws . String ( c . bucketID ) ,
2023-11-14 04:03:01 -05:00
Prefix : aws . String ( reportVersionDir ( attestation ) ) ,
2023-09-25 05:53:02 -04:00
} )
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.
2023-11-14 04:03:01 -05:00
func ( c Client ) findMinVersion ( ctx context . Context , attesation variant . Variant , versionDates [ ] string ) ( SEVSNPVersion , string , error ) {
var minimalVersion * SEVSNPVersion
2023-09-25 05:53:02 -04:00
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 {
2023-11-14 04:03:01 -05:00
obj , err := client . Fetch ( ctx , c . s3Client , reportedSEVSNPVersionAPI { Version : date + ".json" , variant : attesation } )
2023-09-25 05:53:02 -04:00
if err != nil {
2023-11-14 04:03:01 -05:00
return SEVSNPVersion { } , "" , fmt . Errorf ( "get object: %w" , err )
2023-09-25 05:53:02 -04:00
}
2023-11-14 04:03:01 -05:00
// Need to set this explicitly as the variant is not part of the marshalled JSON.
obj . variant = attesation
2023-09-25 05:53:02 -04:00
if minimalVersion == nil {
2023-11-14 04:03:01 -05:00
minimalVersion = & obj . SEVSNPVersion
2023-09-25 05:53:02 -04:00
minimalDate = date
} else {
2023-11-14 04:03:01 -05:00
shouldUpdateMinimal , err := isInputNewerThanOtherVersion ( * minimalVersion , obj . SEVSNPVersion )
2023-09-25 05:53:02 -04:00
if err != nil {
continue
}
if shouldUpdateMinimal {
2023-11-14 04:03:01 -05:00
minimalVersion = & obj . SEVSNPVersion
2023-09-25 05:53:02 -04:00
minimalDate = date
}
}
}
return * minimalVersion , minimalDate , nil
}
// isInputNewerThanOtherVersion compares all version fields and returns true if any input field is newer.
2023-11-14 04:03:01 -05:00
func isInputNewerThanOtherVersion ( input , other SEVSNPVersion ) ( bool , error ) {
2023-09-25 05:53:02 -04:00
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
}
2023-11-14 04:03:01 -05:00
// 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
2023-09-25 05:53:02 -04:00
}
// JSONPath returns the path to the JSON file for the request to the config api.
2023-11-14 04:03:01 -05:00
func ( i reportedSEVSNPVersionAPI ) JSONPath ( ) string {
return path . Join ( reportVersionDir ( i . variant ) , i . Version )
2023-09-25 05:53:02 -04:00
}
// ValidateRequest validates the request.
2023-11-14 04:03:01 -05:00
func ( i reportedSEVSNPVersionAPI ) ValidateRequest ( ) error {
2023-09-25 05:53:02 -04:00
if ! strings . HasSuffix ( i . Version , ".json" ) {
return fmt . Errorf ( "version has no .json suffix" )
}
return nil
}
// Validate is a No-Op at the moment.
2023-11-14 04:03:01 -05:00
func ( i reportedSEVSNPVersionAPI ) Validate ( ) error {
2023-09-25 05:53:02 -04:00
return nil
}