2023-06-01 07:55:46 -04:00
/ *
Copyright ( c ) Edgeless Systems GmbH
SPDX - License - Identifier : AGPL - 3.0 - only
* /
2023-08-23 10:39:49 -04:00
/ *
2023-08-31 04:46:50 -04:00
This package provides a CLI to interact with the Attestationconfig API , a sub API of the Resource API .
2023-08-23 10:39:49 -04:00
2023-09-11 04:57:32 -04:00
You can execute an e2e test by running : ` bazel run //internal/api/attestationconfigapi:configapi_e2e_test ` .
2023-09-25 05:53:02 -04:00
The CLI is used in the CI pipeline . Manual actions that change the bucket ' s data shouldn ' t be necessary .
The reporter CLI caches the observed version values in a dedicated caching directory and derives the latest API version from it .
Any version update is then pushed to the API .
2023-08-23 10:39:49 -04:00
* /
2023-08-10 03:45:46 -04:00
package main
2023-06-01 07:55:46 -04:00
import (
2023-08-31 04:31:45 -04:00
"errors"
2023-06-01 07:55:46 -04:00
"fmt"
"os"
"time"
2023-06-07 10:16:32 -04:00
"github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi"
2023-08-23 10:39:49 -04:00
"github.com/edgelesssys/constellation/v2/internal/constants"
2023-10-17 11:36:50 -04:00
"github.com/edgelesssys/constellation/v2/internal/file"
2023-06-05 06:33:22 -04:00
"github.com/edgelesssys/constellation/v2/internal/logger"
2023-08-25 06:40:47 -04:00
"github.com/edgelesssys/constellation/v2/internal/staticupload"
2023-10-17 11:36:50 -04:00
"github.com/edgelesssys/constellation/v2/internal/verify"
"github.com/spf13/afero"
2023-06-01 07:55:46 -04:00
"github.com/spf13/cobra"
2023-10-17 11:36:50 -04:00
"go.uber.org/zap"
2023-06-01 07:55:46 -04:00
)
const (
2023-06-02 06:10:22 -04:00
awsRegion = "eu-central-1"
awsBucket = "cdn-constellation-backend"
2023-08-23 10:39:49 -04:00
distributionID = constants . CDNDefaultDistributionID
2023-06-02 06:10:22 -04:00
envCosignPwd = "COSIGN_PASSWORD"
envCosignPrivateKey = "COSIGN_PRIVATE_KEY"
2023-09-25 05:53:02 -04:00
// versionWindowSize defines the number of versions to be considered for the latest version. Each week 5 versions are uploaded for each node of the verify cluster.
versionWindowSize = 15
2023-06-01 07:55:46 -04:00
)
var (
// Cosign credentials.
2023-06-02 06:10:22 -04:00
cosignPwd string
privateKey string
2023-06-01 07:55:46 -04:00
)
2023-08-10 03:45:46 -04:00
func main ( ) {
if err := newRootCmd ( ) . Execute ( ) ; err != nil {
os . Exit ( 1 )
}
os . Exit ( 0 )
2023-06-01 07:55:46 -04:00
}
// newRootCmd creates the root command.
func newRootCmd ( ) * cobra . Command {
rootCmd := & cobra . Command {
2023-09-25 05:53:02 -04:00
Use : "COSIGN_PASSWORD=$CPW COSIGN_PRIVATE_KEY=$CKEY 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-09-25 05:53:02 -04:00
Long : fmt . Sprintf ( "The CLI uploads an observed version number specific to the azure-sev-snp attestation variant to a cache directory. The CLI then determines the lowest version within the cache-window present in the cache and writes that value to the config api if necessary. " +
2023-08-09 12:58:46 -04:00
"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 ,
) ,
2023-06-02 06:10:22 -04:00
PreRunE : envCheck ,
RunE : runCmd ,
2023-06-01 07:55:46 -04:00
}
2023-10-17 11:36:50 -04:00
rootCmd . Flags ( ) . StringP ( "snp-report-path" , "t" , "" , "File path to a file containing the Constellation verify output." )
2023-08-09 12:58:46 -04:00
rootCmd . Flags ( ) . StringP ( "upload-date" , "d" , "" , "upload a version with this date as version name." )
2023-09-25 05:53:02 -04:00
rootCmd . Flags ( ) . BoolP ( "force" , "f" , false , "Use force to manually push a new latest version." +
" The version gets saved to the cache but the version selection logic is skipped." )
rootCmd . Flags ( ) . IntP ( "cache-window-size" , "s" , versionWindowSize , "Number of versions to be considered for the latest version." )
2023-08-23 09:10:20 -04:00
rootCmd . PersistentFlags ( ) . StringP ( "region" , "r" , awsRegion , "region of the targeted bucket." )
rootCmd . PersistentFlags ( ) . StringP ( "bucket" , "b" , awsBucket , "bucket targeted by all operations." )
2023-09-25 05:53:02 -04:00
rootCmd . PersistentFlags ( ) . Bool ( "testing" , false , "upload to S3 test bucket." )
2023-10-17 11:36:50 -04:00
must ( rootCmd . MarkFlagRequired ( "snp-report-path" ) )
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-08-23 10:39:49 -04:00
func runCmd ( cmd * cobra . Command , _ [ ] string ) ( retErr error ) {
2023-06-01 07:55:46 -04:00
ctx := cmd . Context ( )
2023-08-09 12:58:46 -04:00
log := logger . New ( logger . PlainLog , zap . DebugLevel ) . Named ( "attestationconfigapi" )
2023-08-23 09:10:20 -04:00
flags , err := parseCliFlags ( cmd )
if err != nil {
return fmt . Errorf ( "parsing cli flags: %w" , err )
}
2023-06-01 07:55:46 -04:00
cfg := staticupload . Config {
2023-08-23 10:39:49 -04:00
Bucket : flags . bucket ,
Region : flags . region ,
DistributionID : flags . distribution ,
2023-06-01 07:55:46 -04:00
}
2023-08-23 09:10:20 -04:00
2023-10-17 11:36:50 -04:00
log . Infof ( "Reading SNP report from file: %s" , flags . snpReportPath )
fs := file . NewHandler ( afero . NewOsFs ( ) )
var report verify . Report
if err := fs . ReadJSON ( flags . snpReportPath , & report ) ; err != nil {
return fmt . Errorf ( "reading snp report: %w" , err )
2023-06-01 07:55:46 -04:00
}
2023-10-17 11:36:50 -04:00
snpReport := report . SNPReport
if ! allEqual ( snpReport . LaunchTCB , snpReport . CommittedTCB , snpReport . ReportedTCB ) {
return fmt . Errorf ( "TCB versions are not equal: \nLaunchTCB:%+v\nCommitted TCB:%+v\nReportedTCB:%+v" ,
snpReport . LaunchTCB , snpReport . CommittedTCB , snpReport . ReportedTCB )
2023-06-01 07:55:46 -04:00
}
2023-10-17 11:36:50 -04:00
inputVersion := convertTCBVersionToAzureVersion ( snpReport . LaunchTCB )
log . Infof ( "Input report: %+v" , inputVersion )
2023-06-01 07:55:46 -04:00
2023-09-25 05:53:02 -04:00
client , clientClose , err := attestationconfigapi . NewClient ( ctx , cfg ,
[ ] byte ( cosignPwd ) , [ ] byte ( privateKey ) , false , flags . cacheWindowSize , log )
2023-08-31 04:31:45 -04:00
defer func ( ) {
err := clientClose ( cmd . Context ( ) )
if err != nil {
retErr = errors . Join ( retErr , fmt . Errorf ( "failed to invalidate cache: %w" , err ) )
2023-06-02 06:10:22 -04:00
}
2023-08-31 04:31:45 -04:00
} ( )
2023-08-23 10:39:49 -04:00
2023-08-09 12:58:46 -04:00
if err != nil {
return fmt . Errorf ( "creating client: %w" , err )
2023-06-01 07:55:46 -04:00
}
2023-08-09 12:58:46 -04:00
2023-10-17 11:36:50 -04:00
latestAPIVersionAPI , err := attestationconfigapi . NewFetcherWithCustomCDNAndCosignKey ( flags . url , flags . cosignPublicKey ) . FetchAzureSEVSNPVersionLatest ( ctx )
2023-09-25 05:53:02 -04:00
if err != nil {
if errors . Is ( err , attestationconfigapi . ErrNoVersionsFound ) {
log . Infof ( "No versions found in API, but assuming that we are uploading the first version." )
} else {
return fmt . Errorf ( "fetching latest version: %w" , err )
}
}
latestAPIVersion := latestAPIVersionAPI . AzureSEVSNPVersion
if err := client . UploadAzureSEVSNPVersionLatest ( ctx , inputVersion , latestAPIVersion , flags . uploadDate , flags . force ) ; err != nil {
if errors . Is ( err , attestationconfigapi . ErrNoNewerVersion ) {
log . Infof ( "Input version: %+v is not newer than latest API version: %+v" , inputVersion , latestAPIVersion )
return nil
}
return fmt . Errorf ( "updating latest version: %w" , err )
2023-08-09 12:58:46 -04:00
}
2023-06-01 07:55:46 -04:00
return nil
}
2023-10-17 11:36:50 -04:00
func allEqual ( args ... verify . TCBVersion ) bool {
if len ( args ) < 2 {
return true
}
firstArg := args [ 0 ]
for _ , arg := range args [ 1 : ] {
if arg != firstArg {
return false
}
}
return true
}
func convertTCBVersionToAzureVersion ( tcb verify . TCBVersion ) attestationconfigapi . AzureSEVSNPVersion {
return attestationconfigapi . AzureSEVSNPVersion {
Bootloader : tcb . Bootloader ,
TEE : tcb . TEE ,
SNP : tcb . SNP ,
Microcode : tcb . Microcode ,
}
}
2023-09-25 05:53:02 -04:00
type config struct {
2023-10-17 11:36:50 -04:00
snpReportPath string
2023-09-25 05:53:02 -04:00
uploadDate time . Time
2023-10-17 11:36:50 -04:00
cosignPublicKey string
2023-09-25 05:53:02 -04:00
region string
bucket string
distribution string
url string
force bool
cacheWindowSize int
2023-08-23 09:10:20 -04:00
}
2023-09-25 05:53:02 -04:00
func parseCliFlags ( cmd * cobra . Command ) ( config , error ) {
2023-10-17 11:36:50 -04:00
snpReportFilePath , err := cmd . Flags ( ) . GetString ( "snp-report-path" )
2023-08-23 09:10:20 -04:00
if err != nil {
2023-09-25 05:53:02 -04:00
return config { } , fmt . Errorf ( "getting maa claims path: %w" , err )
2023-08-23 09:10:20 -04:00
}
dateStr , err := cmd . Flags ( ) . GetString ( "upload-date" )
if err != nil {
2023-09-25 05:53:02 -04:00
return config { } , fmt . Errorf ( "getting upload date: %w" , err )
2023-08-23 09:10:20 -04:00
}
uploadDate := time . Now ( )
if dateStr != "" {
uploadDate , err = time . Parse ( attestationconfigapi . VersionFormat , dateStr )
if err != nil {
2023-09-25 05:53:02 -04:00
return config { } , fmt . Errorf ( "parsing date: %w" , err )
2023-08-23 09:10:20 -04:00
}
}
region , err := cmd . Flags ( ) . GetString ( "region" )
if err != nil {
2023-09-25 05:53:02 -04:00
return config { } , fmt . Errorf ( "getting region: %w" , err )
2023-08-23 09:10:20 -04:00
}
bucket , err := cmd . Flags ( ) . GetString ( "bucket" )
if err != nil {
2023-09-25 05:53:02 -04:00
return config { } , fmt . Errorf ( "getting bucket: %w" , err )
2023-08-23 09:10:20 -04:00
}
2023-09-25 05:53:02 -04:00
testing , err := cmd . Flags ( ) . GetBool ( "testing" )
2023-08-23 10:39:49 -04:00
if err != nil {
2023-09-25 05:53:02 -04:00
return config { } , fmt . Errorf ( "getting testing flag: %w" , err )
2023-08-23 10:39:49 -04:00
}
2023-10-17 11:36:50 -04:00
apiCfg := getAPIEnvironment ( testing )
2023-08-23 10:39:49 -04:00
2023-09-25 05:53:02 -04:00
force , err := cmd . Flags ( ) . GetBool ( "force" )
if err != nil {
return config { } , fmt . Errorf ( "getting force: %w" , err )
}
cacheWindowSize , err := cmd . Flags ( ) . GetInt ( "cache-window-size" )
if err != nil {
return config { } , fmt . Errorf ( "getting cache window size: %w" , err )
}
return config {
2023-10-17 11:36:50 -04:00
snpReportPath : snpReportFilePath ,
2023-09-25 05:53:02 -04:00
uploadDate : uploadDate ,
2023-10-17 11:36:50 -04:00
cosignPublicKey : apiCfg . cosignPublicKey ,
2023-09-25 05:53:02 -04:00
region : region ,
bucket : bucket ,
2023-10-17 11:36:50 -04:00
url : apiCfg . url ,
distribution : apiCfg . distribution ,
2023-09-25 05:53:02 -04:00
force : force ,
cacheWindowSize : cacheWindowSize ,
2023-08-23 09:10:20 -04:00
} , nil
}
2023-10-17 11:36:50 -04:00
type apiConfig struct {
url string
distribution string
cosignPublicKey string
2023-08-09 12:58:46 -04:00
}
2023-10-17 11:36:50 -04:00
func getAPIEnvironment ( testing bool ) apiConfig {
if testing {
return apiConfig { url : "https://d33dzgxuwsgbpw.cloudfront.net" , distribution : "ETZGUP1CWRC2P" , cosignPublicKey : constants . CosignPublicKeyDev }
2023-06-09 06:48:12 -04:00
}
2023-10-17 11:36:50 -04:00
return apiConfig { url : constants . CDNRepositoryURL , distribution : constants . CDNDefaultDistributionID , cosignPublicKey : constants . CosignPublicKeyReleases }
2023-06-09 06:48:12 -04:00
}
2023-06-01 07:55:46 -04:00
func must ( err error ) {
if err != nil {
panic ( err )
}
}