2022-09-05 03:06:08 -04:00
/ *
Copyright ( c ) Edgeless Systems GmbH
SPDX - License - Identifier : AGPL - 3.0 - only
* /
2022-08-01 03:37:05 -04:00
package cmd
import (
"context"
2023-02-07 06:56:25 -05:00
"errors"
2022-08-01 03:37:05 -04:00
"fmt"
"net/http"
"net/url"
"time"
2023-05-26 11:50:55 -04:00
"github.com/edgelesssys/constellation/v2/cli/internal/featureset"
2023-05-25 12:43:44 -04:00
"github.com/edgelesssys/constellation/v2/internal/api/versionsapi"
2022-11-15 09:40:49 -05:00
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
2022-09-21 07:47:57 -04:00
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/file"
2022-10-11 07:57:52 -04:00
"github.com/edgelesssys/constellation/v2/internal/sigstore"
2022-08-01 03:37:05 -04:00
"github.com/spf13/afero"
"github.com/spf13/cobra"
)
func newConfigFetchMeasurementsCmd ( ) * cobra . Command {
cmd := & cobra . Command {
Use : "fetch-measurements" ,
Short : "Fetch measurements for configured cloud provider and image" ,
2023-01-17 08:01:56 -05:00
Long : "Fetch measurements for configured cloud provider and image.\n\n" +
"A config needs to be generated first." ,
Args : cobra . ExactArgs ( 0 ) ,
RunE : runConfigFetchMeasurements ,
2022-08-01 03:37:05 -04:00
}
cmd . Flags ( ) . StringP ( "url" , "u" , "" , "alternative URL to fetch measurements from" )
cmd . Flags ( ) . StringP ( "signature-url" , "s" , "" , "alternative URL to fetch measurements' signature from" )
return cmd
}
type fetchMeasurementsFlags struct {
measurementsURL * url . URL
signatureURL * url . URL
2022-11-15 09:40:49 -05:00
configPath string
2023-01-31 05:45:31 -05:00
force bool
2022-08-01 03:37:05 -04:00
}
2023-01-04 04:46:29 -05:00
type configFetchMeasurementsCmd struct {
2023-05-26 11:50:55 -04:00
canFetchMeasurements bool
log debugLog
2023-01-04 04:46:29 -05:00
}
2023-03-20 06:03:36 -04:00
func runConfigFetchMeasurements ( cmd * cobra . Command , _ [ ] string ) error {
2023-01-04 04:46:29 -05:00
log , err := newCLILogger ( cmd )
if err != nil {
return fmt . Errorf ( "creating logger: %w" , err )
}
defer log . Sync ( )
2022-08-01 03:37:05 -04:00
fileHandler := file . NewHandler ( afero . NewOsFs ( ) )
2022-10-11 07:57:52 -04:00
rekor , err := sigstore . NewRekor ( )
if err != nil {
return fmt . Errorf ( "constructing Rekor client: %w" , err )
}
2023-05-26 11:50:55 -04:00
cfm := & configFetchMeasurementsCmd { log : log , canFetchMeasurements : featureset . CanFetchMeasurements }
2023-01-04 04:46:29 -05:00
2023-05-26 11:49:46 -04:00
return cfm . configFetchMeasurements ( cmd , sigstore . CosignVerifier { } , rekor , fileHandler , http . DefaultClient )
2022-08-01 03:37:05 -04:00
}
2023-01-04 04:46:29 -05:00
func ( cfm * configFetchMeasurementsCmd ) configFetchMeasurements (
2023-05-26 11:49:46 -04:00
cmd * cobra . Command , cosign cosignVerifier , rekor rekorVerifier ,
2022-11-28 04:27:33 -05:00
fileHandler file . Handler , client * http . Client ,
) error {
2023-01-04 04:46:29 -05:00
flags , err := cfm . parseFetchMeasurementsFlags ( cmd )
2022-08-01 03:37:05 -04:00
if err != nil {
return err
}
2023-01-18 07:10:24 -05:00
cfm . log . Debugf ( "Using flags %v" , flags )
2022-08-01 03:37:05 -04:00
2023-05-26 11:50:55 -04:00
if ! cfm . canFetchMeasurements {
cmd . PrintErrln ( "Fetching measurements is not supported in the OSS build of the Constellation CLI. Consult the documentation for instructions on where to download the enterprise version." )
return errors . New ( "fetching measurements is not supported" )
}
2023-01-18 07:10:24 -05:00
cfm . log . Debugf ( "Loading configuration file from %q" , flags . configPath )
2023-05-25 12:43:44 -04:00
conf , err := config . NewWithClient ( fileHandler , flags . configPath , client , flags . force )
2023-02-07 06:56:25 -05:00
var configValidationErr * config . ValidationError
if errors . As ( err , & configValidationErr ) {
cmd . PrintErrln ( configValidationErr . LongMessage ( ) )
}
2022-08-01 03:37:05 -04:00
if err != nil {
2023-02-07 06:56:25 -05:00
return err
2022-08-01 03:37:05 -04:00
}
2022-11-22 12:47:08 -05:00
if ! conf . IsReleaseImage ( ) {
2022-11-10 04:27:24 -05:00
cmd . PrintErrln ( "Configured image doesn't look like a released production image. Double check image before deploying to production." )
2022-08-16 09:53:54 -04:00
}
2023-01-04 04:46:29 -05:00
cfm . log . Debugf ( "Creating context" )
2022-11-22 12:47:08 -05:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , time . Minute )
defer cancel ( )
2023-01-04 04:46:29 -05:00
cfm . log . Debugf ( "Updating URLs" )
2022-11-28 04:27:33 -05:00
if err := flags . updateURLs ( conf ) ; err != nil {
2022-08-01 03:37:05 -04:00
return err
}
2023-01-04 04:46:29 -05:00
cfm . log . Debugf ( "Fetching and verifying measurements" )
2023-05-22 08:59:28 -04:00
imageVersion , err := versionsapi . NewVersionFromShortPath ( conf . Image , versionsapi . VersionKindImage )
if err != nil {
return err
}
2022-11-15 09:40:49 -05:00
var fetchedMeasurements measurements . M
2022-11-28 04:27:33 -05:00
hash , err := fetchedMeasurements . FetchAndVerify (
2023-05-26 11:49:46 -04:00
ctx , client , cosign ,
2022-11-28 04:27:33 -05:00
flags . measurementsURL ,
flags . signatureURL ,
2023-05-22 08:59:28 -04:00
imageVersion ,
conf . GetProvider ( ) ,
conf . GetAttestationConfig ( ) . GetVariant ( ) ,
2022-11-28 04:27:33 -05:00
)
2022-10-11 07:57:52 -04:00
if err != nil {
2022-08-01 03:37:05 -04:00
return err
}
2023-01-04 04:46:29 -05:00
cfm . log . Debugf ( "Fetched and verified measurements, hash is %s" , hash )
2023-05-26 11:49:46 -04:00
if err := sigstore . VerifyWithRekor ( cmd . Context ( ) , imageVersion , rekor , hash ) ; err != nil {
2022-11-10 04:27:24 -05:00
cmd . PrintErrf ( "Ignoring Rekor related error: %v\n" , err )
cmd . PrintErrln ( "Make sure the downloaded measurements are trustworthy!" )
2022-10-11 07:57:52 -04:00
}
2023-01-18 07:10:24 -05:00
cfm . log . Debugf ( "Verified measurements with Rekor, updating measurements in configuration" )
2022-08-01 03:37:05 -04:00
conf . UpdateMeasurements ( fetchedMeasurements )
2022-11-15 09:40:49 -05:00
if err := fileHandler . WriteYAML ( flags . configPath , conf , file . OptOverwrite ) ; err != nil {
2022-08-01 03:37:05 -04:00
return err
}
2023-03-03 10:50:25 -05:00
cfm . log . Debugf ( "Configuration written to %s" , flags . configPath )
2022-08-01 03:37:05 -04:00
return nil
}
// parseURLFlag checks that flag can be parsed as URL.
// If no value was provided for flag, nil is returned.
2023-01-04 04:46:29 -05:00
func ( cfm * configFetchMeasurementsCmd ) parseURLFlag ( cmd * cobra . Command , flag string ) ( * url . URL , error ) {
2022-08-01 03:37:05 -04:00
rawURL , err := cmd . Flags ( ) . GetString ( flag )
if err != nil {
return nil , fmt . Errorf ( "parsing config generate flags '%s': %w" , flag , err )
}
2023-01-18 07:10:24 -05:00
cfm . log . Debugf ( "Flag %s has raw URL %q" , flag , rawURL )
2022-08-01 03:37:05 -04:00
if rawURL != "" {
2023-01-18 07:10:24 -05:00
cfm . log . Debugf ( "Parsing raw URL" )
2022-08-01 03:37:05 -04:00
return url . Parse ( rawURL )
}
return nil , nil
}
2023-01-04 04:46:29 -05:00
func ( cfm * configFetchMeasurementsCmd ) parseFetchMeasurementsFlags ( cmd * cobra . Command ) ( * fetchMeasurementsFlags , error ) {
measurementsURL , err := cfm . parseURLFlag ( cmd , "url" )
2022-08-01 03:37:05 -04:00
if err != nil {
return & fetchMeasurementsFlags { } , err
}
2023-01-18 07:10:24 -05:00
cfm . log . Debugf ( "Parsed measurements URL as %v" , measurementsURL )
2022-08-01 03:37:05 -04:00
2023-01-04 04:46:29 -05:00
measurementsSignatureURL , err := cfm . parseURLFlag ( cmd , "signature-url" )
2022-08-01 03:37:05 -04:00
if err != nil {
return & fetchMeasurementsFlags { } , err
}
2023-01-18 07:10:24 -05:00
cfm . log . Debugf ( "Parsed measurements signature URL as %v" , measurementsSignatureURL )
2022-08-01 03:37:05 -04:00
config , err := cmd . Flags ( ) . GetString ( "config" )
if err != nil {
return & fetchMeasurementsFlags { } , fmt . Errorf ( "parsing config path argument: %w" , err )
}
2023-01-18 07:10:24 -05:00
cfm . log . Debugf ( "Configuration path is %q" , config )
2022-08-01 03:37:05 -04:00
2023-01-31 05:45:31 -05:00
force , err := cmd . Flags ( ) . GetBool ( "force" )
if err != nil {
return & fetchMeasurementsFlags { } , fmt . Errorf ( "parsing force argument: %w" , err )
}
2022-08-01 03:37:05 -04:00
return & fetchMeasurementsFlags {
measurementsURL : measurementsURL ,
signatureURL : measurementsSignatureURL ,
2022-11-15 09:40:49 -05:00
configPath : config ,
2023-01-31 05:45:31 -05:00
force : force ,
2022-08-01 03:37:05 -04:00
} , nil
}
2022-11-28 04:27:33 -05:00
func ( f * fetchMeasurementsFlags ) updateURLs ( conf * config . Config ) error {
2023-02-03 05:05:42 -05:00
ver , err := versionsapi . NewVersionFromShortPath ( conf . Image , versionsapi . VersionKindImage )
if err != nil {
return fmt . Errorf ( "creating version from image name: %w" , err )
}
2023-05-22 08:59:28 -04:00
measurementsURL , signatureURL , err := versionsapi . MeasurementURL ( ver )
2023-02-03 05:05:42 -05:00
if err != nil {
return err
}
2022-08-01 03:37:05 -04:00
if f . measurementsURL == nil {
2023-02-03 05:05:42 -05:00
f . measurementsURL = measurementsURL
2022-08-01 03:37:05 -04:00
}
if f . signatureURL == nil {
2023-02-03 05:05:42 -05:00
f . signatureURL = signatureURL
2022-08-01 03:37:05 -04:00
}
return nil
}
2023-05-26 11:49:46 -04:00
type rekorVerifier interface {
SearchByHash ( context . Context , string ) ( [ ] string , error )
VerifyEntry ( context . Context , string , string ) error
}
type cosignVerifier interface {
VerifySignature ( content , signature , publicKey [ ] byte ) error
}