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