2023-06-01 07:55:46 -04:00
/ *
Copyright ( c ) Edgeless Systems GmbH
SPDX - License - Identifier : AGPL - 3.0 - only
* /
package cmd
import (
"encoding/json"
"fmt"
"os"
2023-06-02 06:10:22 -04:00
"reflect"
2023-06-01 07:55:46 -04:00
"time"
2023-06-07 10:16:32 -04:00
"github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi"
2023-06-05 06:33:22 -04:00
"github.com/edgelesssys/constellation/v2/internal/logger"
"go.uber.org/zap"
2023-06-02 06:10:22 -04:00
2023-06-01 07:55:46 -04:00
"github.com/edgelesssys/constellation/v2/internal/staticupload"
"github.com/spf13/cobra"
)
const (
2023-06-02 06:10:22 -04:00
awsRegion = "eu-central-1"
awsBucket = "cdn-constellation-backend"
invalidDefault = 0
envAwsKeyID = "AWS_ACCESS_KEY_ID"
envAwsKey = "AWS_ACCESS_KEY"
envCosignPwd = "COSIGN_PASSWORD"
envCosignPrivateKey = "COSIGN_PRIVATE_KEY"
2023-06-01 07:55:46 -04:00
)
var (
versionFilePath string
2023-06-05 10:10:44 -04:00
force bool
2023-06-01 07:55:46 -04:00
// Cosign credentials.
2023-06-02 06:10:22 -04:00
cosignPwd string
privateKey string
2023-06-01 07:55:46 -04:00
)
// Execute executes the root command.
func Execute ( ) error {
return newRootCmd ( ) . Execute ( )
}
// newRootCmd creates the root command.
func newRootCmd ( ) * cobra . Command {
rootCmd := & cobra . Command {
2023-06-02 06:10:22 -04:00
Use : "COSIGN_PASSWORD=$CPWD COSIGN_PRIVATE_KEY=$CKEY AWS_ACCESS_KEY_ID=$ID AWS_ACCESS_KEY=$KEY upload --version-file $FILE" ,
2023-06-01 07:55:46 -04:00
Short : "Upload a set of versions specific to the azure-sev-snp attestation variant to the config api." ,
2023-06-02 06:10:22 -04:00
Long : fmt . Sprintf ( "Upload a set of versions specific to the azure-sev-snp attestation variant to the config api. Please authenticate with AWS through your preferred method (e.g. environment variables, CLI) to be able to upload to S3. Set the %s and %s environment variables to authenticate with cosign." , envCosignPrivateKey , envCosignPwd ) ,
PreRunE : envCheck ,
RunE : runCmd ,
2023-06-01 07:55:46 -04:00
}
2023-06-05 10:10:44 -04:00
rootCmd . Flags ( ) . StringVarP ( & versionFilePath , "version-file" , "f" , "" , "File path to the version json file." )
rootCmd . Flags ( ) . BoolVar ( & force , "force" , false , "force to upload version regardless of comparison to latest API value." )
2023-06-09 06:48:12 -04:00
rootCmd . Flags ( ) . StringP ( "upload-date" , "d" , "" , "upload a version with this date as version name. Setting it implies --force." )
2023-06-05 10:10:44 -04:00
must ( enforceRequiredFlags ( rootCmd , "version-file" ) )
2023-06-05 06:33:22 -04:00
rootCmd . AddCommand ( newDeleteCmd ( ) )
2023-06-01 07:55:46 -04:00
return rootCmd
}
2023-06-02 06:10:22 -04:00
func envCheck ( _ * cobra . Command , _ [ ] string ) error {
if os . Getenv ( envCosignPrivateKey ) == "" || os . Getenv ( envCosignPwd ) == "" {
return fmt . Errorf ( "please set both %s and %s environment variables" , envCosignPrivateKey , envCosignPwd )
}
cosignPwd = os . Getenv ( envCosignPwd )
privateKey = os . Getenv ( envCosignPrivateKey )
return nil
}
2023-06-01 07:55:46 -04:00
func runCmd ( cmd * cobra . Command , _ [ ] string ) error {
ctx := cmd . Context ( )
cfg := staticupload . Config {
Bucket : awsBucket ,
Region : awsRegion ,
}
versionBytes , err := os . ReadFile ( versionFilePath )
if err != nil {
return fmt . Errorf ( "reading version file: %w" , err )
}
2023-06-07 10:16:32 -04:00
var inputVersion attestationconfigapi . AzureSEVSNPVersion
2023-06-02 06:10:22 -04:00
if err = json . Unmarshal ( versionBytes , & inputVersion ) ; err != nil {
2023-06-01 07:55:46 -04:00
return fmt . Errorf ( "unmarshalling version file: %w" , err )
}
2023-06-09 06:48:12 -04:00
dateStr , err := cmd . Flags ( ) . GetString ( "upload-date" )
2023-06-01 07:55:46 -04:00
if err != nil {
2023-06-09 06:48:12 -04:00
return fmt . Errorf ( "getting upload date: %w" , err )
2023-06-01 07:55:46 -04:00
}
2023-06-09 06:48:12 -04:00
var uploadDate time . Time
if dateStr != "" {
uploadDate , err = time . Parse ( "2006-01-01-01-01" , dateStr )
if err != nil {
return fmt . Errorf ( "parsing date: %w" , err )
}
} else {
uploadDate = time . Now ( )
force = true
2023-06-02 06:10:22 -04:00
}
2023-06-09 06:48:12 -04:00
doUpload := false
if ! force {
latestAPIVersion , err := attestationconfigapi . NewFetcher ( ) . FetchAzureSEVSNPVersionLatest ( ctx , time . Now ( ) )
if err != nil {
return fmt . Errorf ( "fetching latest version: %w" , err )
2023-06-05 10:10:44 -04:00
}
2023-06-09 06:48:12 -04:00
isNewer , err := isInputNewerThanLatestAPI ( inputVersion , latestAPIVersion . AzureSEVSNPVersion )
if err != nil {
return fmt . Errorf ( "comparing versions: %w" , err )
}
cmd . Print ( versionComparisonInformation ( isNewer , inputVersion , latestAPIVersion . AzureSEVSNPVersion ) )
doUpload = isNewer
} else {
doUpload = true
cmd . Println ( "Forcing upload of new version" )
}
if doUpload {
2023-06-07 10:16:32 -04:00
sut , sutClose , err := attestationconfigapi . NewClient ( ctx , cfg , [ ] byte ( cosignPwd ) , [ ] byte ( privateKey ) , false , log ( ) )
2023-06-02 06:10:22 -04:00
defer func ( ) {
if err := sutClose ( ctx ) ; err != nil {
2023-06-05 06:33:22 -04:00
cmd . Printf ( "closing repo: %v\n" , err )
2023-06-02 06:10:22 -04:00
}
} ( )
if err != nil {
return fmt . Errorf ( "creating repo: %w" , err )
2023-06-02 05:20:01 -04:00
}
2023-06-01 07:55:46 -04:00
2023-06-09 06:48:12 -04:00
if err := sut . UploadAzureSEVSNP ( ctx , inputVersion , uploadDate ) ; err != nil {
2023-06-02 06:10:22 -04:00
return fmt . Errorf ( "uploading version: %w" , err )
}
cmd . Printf ( "Successfully uploaded new Azure SEV-SNP version: %+v\n" , inputVersion )
2023-06-01 07:55:46 -04:00
}
return nil
}
2023-06-09 06:48:12 -04:00
func versionComparisonInformation ( isNewer bool , inputVersion attestationconfigapi . AzureSEVSNPVersion , latestAPIVersion attestationconfigapi . AzureSEVSNPVersion ) string {
if isNewer {
return fmt . Sprintf ( "Input version: %+v is newer than latest API version: %+v\n" , inputVersion , latestAPIVersion )
}
return fmt . Sprintf ( "Input version: %+v is not newer than latest API version: %+v\n" , inputVersion , latestAPIVersion )
}
2023-06-02 06:10:22 -04:00
// isInputNewerThanLatestAPI compares all version fields with the latest API version and returns true if any input field is newer.
2023-06-07 10:16:32 -04:00
func isInputNewerThanLatestAPI ( input , latest attestationconfigapi . AzureSEVSNPVersion ) ( bool , error ) {
2023-06-02 06:10:22 -04:00
inputValues := reflect . ValueOf ( input )
latestValues := reflect . ValueOf ( latest )
fields := reflect . TypeOf ( input )
num := fields . NumField ( )
// validate that no input field is smaller than latest
for i := 0 ; i < num ; i ++ {
field := fields . Field ( i )
inputValue := inputValues . Field ( i ) . Uint ( )
latestValue := latestValues . Field ( i ) . Uint ( )
if inputValue < latestValue {
return false , fmt . Errorf ( "input %s version: %d is older than latest API version: %d" , field . Name , inputValue , latestValue )
} else if inputValue > latestValue {
return true , nil
}
}
// check if any input field is greater than latest
for i := 0 ; i < num ; i ++ {
inputValue := inputValues . Field ( i ) . Uint ( )
latestValue := latestValues . Field ( i ) . Uint ( )
if inputValue > latestValue {
return true , nil
}
}
return false , nil
}
2023-06-01 07:55:46 -04:00
func enforceRequiredFlags ( cmd * cobra . Command , flags ... string ) error {
2023-06-05 06:33:22 -04:00
for _ , flag := range flags {
if err := cmd . MarkFlagRequired ( flag ) ; err != nil {
return err
}
}
return nil
}
2023-06-01 07:55:46 -04:00
func must ( err error ) {
if err != nil {
panic ( err )
}
}
2023-06-05 06:33:22 -04:00
func log ( ) * logger . Logger {
2023-06-07 10:16:32 -04:00
return logger . New ( logger . PlainLog , zap . DebugLevel ) . Named ( "attestationconfigapi" )
2023-06-05 06:33:22 -04:00
}