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 (
"encoding/json"
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-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-06-05 06:33:22 -04:00
"go.uber.org/zap"
2023-06-02 06:10:22 -04:00
2023-06-01 07:55:46 -04:00
"github.com/spf13/cobra"
)
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-08-23 09:10:20 -04:00
rootCmd . Flags ( ) . StringP ( "maa-claims-path" , "t" , "" , "File path to a json file containing the MAA claims." )
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-08-09 12:58:46 -04:00
must ( rootCmd . MarkFlagRequired ( "maa-claims-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
log . Infof ( "Reading MAA claims from file: %s" , flags . maaFilePath )
maaClaimsBytes , err := os . ReadFile ( flags . maaFilePath )
2023-06-01 07:55:46 -04:00
if err != nil {
2023-08-09 12:58:46 -04:00
return fmt . Errorf ( "reading MAA claims file: %w" , err )
2023-06-01 07:55:46 -04:00
}
2023-08-09 12:58:46 -04:00
var maaTCB maaTokenTCBClaims
if err = json . Unmarshal ( maaClaimsBytes , & maaTCB ) ; err != nil {
return fmt . Errorf ( "unmarshalling MAA claims file: %w" , err )
2023-06-01 07:55:46 -04:00
}
2023-08-09 12:58:46 -04:00
inputVersion := maaTCB . ToAzureSEVSNPVersion ( )
2023-08-10 03:46:34 -04:00
log . Infof ( "Input version: %+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-09-25 05:53:02 -04:00
latestAPIVersionAPI , err := attestationconfigapi . NewFetcherWithCustomCDNAndCosignKey ( flags . url , constants . CosignPublicKeyDev ) . FetchAzureSEVSNPVersionLatest ( ctx )
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-09-25 05:53:02 -04:00
type config struct {
maaFilePath string
uploadDate time . Time
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-08-23 09:10:20 -04:00
maaFilePath , err := cmd . Flags ( ) . GetString ( "maa-claims-path" )
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-09-25 05:53:02 -04:00
url , distribution := getEnvironment ( 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 {
maaFilePath : maaFilePath ,
uploadDate : uploadDate ,
region : region ,
bucket : bucket ,
url : url ,
distribution : distribution ,
force : force ,
cacheWindowSize : cacheWindowSize ,
2023-08-23 09:10:20 -04:00
} , nil
}
2023-09-25 05:53:02 -04:00
func getEnvironment ( testing bool ) ( url string , distributionID string ) {
if testing {
return "https://d33dzgxuwsgbpw.cloudfront.net" , "ETZGUP1CWRC2P"
}
return constants . CDNRepositoryURL , constants . CDNDefaultDistributionID
}
2023-08-09 12:58:46 -04:00
// maaTokenTCBClaims describes the TCB information in a MAA token.
type maaTokenTCBClaims struct {
2023-08-10 03:46:34 -04:00
IsolationTEE struct {
TEESvn uint8 ` json:"x-ms-sevsnpvm-tee-svn" `
SNPFwSvn uint8 ` json:"x-ms-sevsnpvm-snpfw-svn" `
MicrocodeSvn uint8 ` json:"x-ms-sevsnpvm-microcode-svn" `
BootloaderSvn uint8 ` json:"x-ms-sevsnpvm-bootloader-svn" `
} ` json:"x-ms-isolation-tee" `
2023-08-09 12:58:46 -04:00
}
func ( c maaTokenTCBClaims ) ToAzureSEVSNPVersion ( ) attestationconfigapi . AzureSEVSNPVersion {
return attestationconfigapi . AzureSEVSNPVersion {
2023-08-10 03:46:34 -04:00
TEE : c . IsolationTEE . TEESvn ,
SNP : c . IsolationTEE . SNPFwSvn ,
Microcode : c . IsolationTEE . MicrocodeSvn ,
Bootloader : c . IsolationTEE . BootloaderSvn ,
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 )
}
}