2023-05-25 17:43:44 +01:00
/ *
Copyright ( c ) Edgeless Systems GmbH
SPDX - License - Identifier : AGPL - 3.0 - only
* /
2024-06-11 14:50:38 +02:00
/ *
package client contains code to manage CVM versions in Constellation ' s CDN API .
It is used to upload and delete "latest" versions for AMD SEV - SNP and Intel TDX .
* /
package client
2023-05-25 17:43:44 +01:00
import (
"context"
2023-09-25 11:53:02 +02:00
"errors"
2023-05-25 17:43:44 +01:00
"fmt"
2024-02-08 14:20:01 +00:00
"log/slog"
2023-05-25 17:43:44 +01:00
"time"
2024-06-11 14:50:38 +02:00
"github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi"
2023-06-05 12:33:22 +02:00
apiclient "github.com/edgelesssys/constellation/v2/internal/api/client"
2023-06-09 15:41:02 +02:00
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
2023-06-01 13:55:46 +02:00
"github.com/edgelesssys/constellation/v2/internal/sigstore"
2023-09-25 11:53:02 +02:00
2023-06-01 13:55:46 +02:00
"github.com/edgelesssys/constellation/v2/internal/staticupload"
2023-05-25 17:43:44 +01:00
)
2023-06-12 16:04:54 +02:00
// VersionFormat is the format of the version name in the S3 bucket.
const VersionFormat = "2006-01-02-15-04"
2023-06-02 09:19:23 +02:00
// Client manages (modifies) the version information for the attestation variants.
type Client struct {
2023-09-25 11:53:02 +02:00
s3Client * apiclient . Client
s3ClientClose func ( ctx context . Context ) error
bucketID string
signer sigstore . Signer
cacheWindowSize int
2023-05-25 17:43:44 +01:00
}
2024-06-11 14:50:38 +02:00
// New returns a new Client.
func New ( ctx context . Context , cfg staticupload . Config , cosignPwd , privateKey [ ] byte , dryRun bool , versionWindowSize int , log * slog . Logger ) ( * Client , apiclient . CloseFunc , error ) {
2023-06-05 12:33:22 +02:00
s3Client , clientClose , err := apiclient . NewClient ( ctx , cfg . Region , cfg . Bucket , cfg . DistributionID , dryRun , log )
2023-05-25 17:43:44 +01:00
if err != nil {
2023-06-02 11:20:01 +02:00
return nil , nil , fmt . Errorf ( "failed to create s3 storage: %w" , err )
2023-05-25 17:43:44 +01:00
}
2023-06-05 12:33:22 +02:00
2023-06-02 11:20:01 +02:00
repo := & Client {
2023-09-25 11:53:02 +02:00
s3Client : s3Client ,
s3ClientClose : clientClose ,
signer : sigstore . NewSigner ( cosignPwd , privateKey ) ,
bucketID : cfg . Bucket ,
cacheWindowSize : versionWindowSize ,
2023-06-02 11:20:01 +02:00
}
2023-06-05 12:33:22 +02:00
return repo , clientClose , nil
2023-06-02 11:20:01 +02:00
}
2024-04-16 18:13:47 +02:00
// uploadSEVSNPVersion uploads the latest version numbers of the SEVSNP. Then version name is the UTC timestamp of the date. The /list entry stores the version name + .json suffix.
2024-06-11 14:50:38 +02:00
func ( a Client ) uploadSEVSNPVersion ( ctx context . Context , attestation variant . Variant , version attestationconfigapi . SEVSNPVersion , date time . Time ) error {
2023-11-14 10:03:01 +01:00
versions , err := a . List ( ctx , attestation )
2023-06-01 13:55:46 +02:00
if err != nil {
2023-06-05 16:10:44 +02:00
return fmt . Errorf ( "fetch version list: %w" , err )
2023-06-01 13:55:46 +02:00
}
2023-11-14 10:03:01 +01:00
ops := a . constructUploadCmd ( attestation , version , versions , date )
2023-08-25 15:10:24 +02:00
2023-06-05 16:10:44 +02:00
return executeAllCmds ( ctx , a . s3Client , ops )
2023-06-05 12:33:22 +02:00
}
2023-11-14 10:03:01 +01:00
// DeleteSEVSNPVersion deletes the given version (without .json suffix) from the API.
func ( a Client ) DeleteSEVSNPVersion ( ctx context . Context , attestation variant . Variant , versionStr string ) error {
versions , err := a . List ( ctx , attestation )
2023-06-05 12:33:22 +02:00
if err != nil {
2023-06-05 16:10:44 +02:00
return fmt . Errorf ( "fetch version list: %w" , err )
2023-06-05 12:33:22 +02:00
}
2023-11-14 10:03:01 +01:00
ops , err := a . deleteSEVSNPVersion ( versions , versionStr )
2023-06-01 13:55:46 +02:00
if err != nil {
2023-06-05 12:33:22 +02:00
return err
2023-06-01 13:55:46 +02:00
}
2023-06-05 16:10:44 +02:00
return executeAllCmds ( ctx , a . s3Client , ops )
2023-06-01 13:55:46 +02:00
}
2023-06-25 23:32:39 +02:00
// List returns the list of versions for the given attestation variant.
2024-06-11 14:50:38 +02:00
func ( a Client ) List ( ctx context . Context , attestation variant . Variant ) ( attestationconfigapi . SEVSNPVersionList , error ) {
2024-04-16 18:13:47 +02:00
if ! attestation . Equal ( variant . AzureSEVSNP { } ) &&
! attestation . Equal ( variant . AWSSEVSNP { } ) &&
! attestation . Equal ( variant . GCPSEVSNP { } ) {
2024-06-11 14:50:38 +02:00
return attestationconfigapi . SEVSNPVersionList { } , fmt . Errorf ( "unsupported attestation variant: %s" , attestation )
2023-11-14 10:03:01 +01:00
}
2024-06-11 14:50:38 +02:00
versions , err := apiclient . Fetch ( ctx , a . s3Client , attestationconfigapi . SEVSNPVersionList { Variant : attestation } )
2023-11-14 10:03:01 +01:00
if err != nil {
var notFoundErr * apiclient . NotFoundError
if errors . As ( err , & notFoundErr ) {
2024-06-11 14:50:38 +02:00
return attestationconfigapi . SEVSNPVersionList { Variant : attestation } , nil
2023-06-05 12:33:22 +02:00
}
2024-06-11 14:50:38 +02:00
return attestationconfigapi . SEVSNPVersionList { } , err
2023-05-25 17:43:44 +01:00
}
2023-11-14 10:03:01 +01:00
2024-06-11 14:50:38 +02:00
versions . Variant = attestation
2023-11-14 10:03:01 +01:00
return versions , nil
2023-05-25 17:43:44 +01:00
}
2024-06-11 14:50:38 +02:00
func ( a Client ) deleteSEVSNPVersion ( versions attestationconfigapi . SEVSNPVersionList , versionStr string ) ( ops [ ] crudCmd , err error ) {
2023-06-05 12:33:22 +02:00
versionStr = versionStr + ".json"
ops = append ( ops , deleteCmd {
2024-06-11 14:50:38 +02:00
apiObject : attestationconfigapi . SEVSNPVersionAPI {
Variant : versions . Variant ,
2023-06-05 12:33:22 +02:00
Version : versionStr ,
} ,
} )
removedVersions , err := removeVersion ( versions , versionStr )
if err != nil {
return nil , err
}
ops = append ( ops , putCmd {
apiObject : removedVersions ,
2023-08-25 15:10:24 +02:00
signer : a . signer ,
2023-06-05 12:33:22 +02:00
} )
return ops , nil
}
2024-06-11 14:50:38 +02:00
func ( a Client ) constructUploadCmd ( attestation variant . Variant , version attestationconfigapi . SEVSNPVersion , versionNames attestationconfigapi . SEVSNPVersionList , date time . Time ) [ ] crudCmd {
if ! attestation . Equal ( versionNames . Variant ) {
2023-11-14 10:03:01 +01:00
return nil
}
2023-06-12 16:04:54 +02:00
dateStr := date . Format ( VersionFormat ) + ".json"
2023-08-25 15:10:24 +02:00
var res [ ] crudCmd
2023-06-05 16:10:44 +02:00
2023-08-25 15:10:24 +02:00
res = append ( res , putCmd {
2024-06-11 14:50:38 +02:00
apiObject : attestationconfigapi . SEVSNPVersionAPI { Version : dateStr , Variant : attestation , SEVSNPVersion : version } ,
2023-08-25 15:10:24 +02:00
signer : a . signer ,
} )
2023-06-05 16:10:44 +02:00
2024-06-11 14:50:38 +02:00
versionNames . AddVersion ( dateStr )
2023-11-14 10:03:01 +01:00
2023-08-25 15:10:24 +02:00
res = append ( res , putCmd {
2023-11-14 10:03:01 +01:00
apiObject : versionNames ,
2023-08-25 15:10:24 +02:00
signer : a . signer ,
} )
2023-05-25 17:43:44 +01:00
2023-08-25 15:10:24 +02:00
return res
2023-06-01 13:55:46 +02:00
}
2024-06-11 14:50:38 +02:00
func removeVersion ( list attestationconfigapi . SEVSNPVersionList , versionStr string ) ( removedVersions attestationconfigapi . SEVSNPVersionList , err error ) {
versions := list . List
2023-06-05 12:33:22 +02:00
for i , v := range versions {
if v == versionStr {
if i == len ( versions ) - 1 {
2024-06-11 14:50:38 +02:00
removedVersions = attestationconfigapi . SEVSNPVersionList { List : versions [ : i ] , Variant : list . Variant }
2023-06-05 12:33:22 +02:00
} else {
2024-06-11 14:50:38 +02:00
removedVersions = attestationconfigapi . SEVSNPVersionList { List : append ( versions [ : i ] , versions [ i + 1 : ] ... ) , Variant : list . Variant }
2023-06-05 12:33:22 +02:00
}
return removedVersions , nil
}
2023-06-01 13:55:46 +02:00
}
2024-06-11 14:50:38 +02:00
return attestationconfigapi . SEVSNPVersionList { } , fmt . Errorf ( "version %s not found in list %v" , versionStr , versions )
2023-06-01 13:55:46 +02:00
}
2023-06-05 16:10:44 +02:00
type crudCmd interface {
Execute ( ctx context . Context , c * apiclient . Client ) error
}
2023-06-05 12:33:22 +02:00
type deleteCmd struct {
apiObject apiclient . APIObject
}
func ( d deleteCmd ) Execute ( ctx context . Context , c * apiclient . Client ) error {
2023-08-25 15:10:24 +02:00
return apiclient . DeleteWithSignature ( ctx , c , d . apiObject )
2023-05-25 17:43:44 +01:00
}
2023-06-02 11:20:01 +02:00
2023-06-05 12:33:22 +02:00
type putCmd struct {
apiObject apiclient . APIObject
2023-08-25 15:10:24 +02:00
signer sigstore . Signer
2023-06-02 11:20:01 +02:00
}
2023-06-05 12:33:22 +02:00
func ( p putCmd ) Execute ( ctx context . Context , c * apiclient . Client ) error {
2023-08-25 15:10:24 +02:00
return apiclient . SignAndUpdate ( ctx , c , p . apiObject , p . signer )
2023-06-05 12:33:22 +02:00
}
2023-06-05 16:10:44 +02:00
func executeAllCmds ( ctx context . Context , client * apiclient . Client , cmds [ ] crudCmd ) error {
for _ , cmd := range cmds {
if err := cmd . Execute ( ctx , client ) ; err != nil {
return fmt . Errorf ( "execute operation %+v: %w" , cmd , err )
}
}
return nil
2023-06-05 12:33:22 +02:00
}