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-06-07 10:16:32 -04:00
"github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi"
"github.com/edgelesssys/constellation/v2/internal/api/versionsapi"
2022-11-15 09:40:49 -05:00
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
2023-11-28 11:30:11 -05:00
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
2022-09-21 07:47:57 -04:00
"github.com/edgelesssys/constellation/v2/internal/config"
2023-08-04 07:53:51 -04:00
"github.com/edgelesssys/constellation/v2/internal/constants"
2023-12-08 10:27:04 -05:00
"github.com/edgelesssys/constellation/v2/internal/constellation/featureset"
2022-09-21 07:47:57 -04:00
"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"
2023-10-16 09:05:29 -04:00
"github.com/spf13/pflag"
2022-08-01 03:37:05 -04:00
)
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" )
2023-06-06 04:32:22 -04:00
cmd . Flags ( ) . Bool ( "insecure" , false , "skip the measurement signature verification" )
2023-06-09 04:49:28 -04:00
must ( cmd . Flags ( ) . MarkHidden ( "insecure" ) )
2022-08-01 03:37:05 -04:00
return cmd
}
type fetchMeasurementsFlags struct {
2023-10-16 09:05:29 -04:00
rootFlags
2022-08-01 03:37:05 -04:00
measurementsURL * url . URL
signatureURL * url . URL
2023-06-06 04:32:22 -04:00
insecure bool
2023-10-16 09:05:29 -04:00
}
func ( f * fetchMeasurementsFlags ) parse ( flags * pflag . FlagSet ) error {
var err error
if err := f . rootFlags . parse ( flags ) ; err != nil {
return err
}
f . measurementsURL , err = parseURLFlag ( flags , "url" )
if err != nil {
return err
}
f . signatureURL , err = parseURLFlag ( flags , "signature-url" )
if err != nil {
return err
}
f . insecure , err = flags . GetBool ( "insecure" )
if err != nil {
return fmt . Errorf ( "getting 'insecure' flag: %w" , err )
}
return nil
2022-08-01 03:37:05 -04:00
}
2023-11-28 11:30:11 -05:00
type verifyFetcher interface {
FetchAndVerifyMeasurements ( ctx context . Context ,
image string , csp cloudprovider . Provider , attestationVariant variant . Variant ,
noVerify bool ,
) ( measurements . M , error )
}
2023-01-04 04:46:29 -05:00
type configFetchMeasurementsCmd struct {
2023-10-16 09:05:29 -04:00
flags fetchMeasurementsFlags
2023-05-26 11:50:55 -04:00
canFetchMeasurements bool
log debugLog
2023-11-28 11:30:11 -05:00
verifyFetcher verifyFetcher
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 )
}
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-11-28 11:30:11 -05:00
verifyFetcher := measurements . NewVerifyFetcher ( sigstore . NewCosignVerifier , rekor , http . DefaultClient )
cfm := & configFetchMeasurementsCmd { log : log , canFetchMeasurements : featureset . CanFetchMeasurements , verifyFetcher : verifyFetcher }
2023-10-16 09:05:29 -04:00
if err := cfm . flags . parse ( cmd . Flags ( ) ) ; err != nil {
return fmt . Errorf ( "parsing flags: %w" , err )
}
2024-02-08 09:20:01 -05:00
cfm . log . Debug ( fmt . Sprintf ( "Using flags %+v" , cfm . flags ) )
2023-01-04 04:46:29 -05:00
2023-09-25 05:53:02 -04:00
fetcher := attestationconfigapi . NewFetcherWithClient ( http . DefaultClient , constants . CDNRepositoryURL )
2023-11-28 11:30:11 -05:00
return cfm . configFetchMeasurements ( cmd , fileHandler , fetcher )
2022-08-01 03:37:05 -04:00
}
2023-01-04 04:46:29 -05:00
func ( cfm * configFetchMeasurementsCmd ) configFetchMeasurements (
2023-11-28 11:30:11 -05:00
cmd * cobra . Command , fileHandler file . Handler , fetcher attestationconfigapi . Fetcher ,
2022-11-28 04:27:33 -05:00
) error {
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" )
}
2024-02-08 09:20:01 -05:00
cfm . log . Debug ( fmt . Sprintf ( "Loading configuration file from %q" , cfm . flags . pathPrefixer . PrefixPrintablePath ( constants . ConfigFilename ) ) )
2023-06-01 07:55:46 -04:00
2023-10-16 09:05:29 -04:00
conf , err := config . New ( fileHandler , constants . ConfigFilename , fetcher , cfm . 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
}
2024-02-08 09:20:01 -05:00
cfm . log . Debug ( "Creating context" )
2022-11-22 12:47:08 -05:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , time . Minute )
defer cancel ( )
2024-02-08 09:20:01 -05:00
cfm . log . Debug ( "Updating URLs" )
2023-10-16 09:05:29 -04:00
if err := cfm . flags . updateURLs ( conf ) ; err != nil {
2022-08-01 03:37:05 -04:00
return err
}
2023-11-28 11:30:11 -05:00
fetchedMeasurements , err := cfm . verifyFetcher . FetchAndVerifyMeasurements ( ctx , conf . Image , conf . GetProvider ( ) ,
conf . GetAttestationConfig ( ) . GetVariant ( ) , cfm . flags . insecure )
2023-05-22 08:59:28 -04:00
if err != nil {
2023-11-28 11:30:11 -05:00
var rekorErr * measurements . RekorError
if errors . As ( err , & rekorErr ) {
2023-06-06 04:32:22 -04:00
cmd . PrintErrf ( "Ignoring Rekor related error: %v\n" , err )
cmd . PrintErrln ( "Make sure the downloaded measurements are trustworthy!" )
2023-11-28 11:30:11 -05:00
} else {
return fmt . Errorf ( "fetching and verifying measurements: %w" , err )
2023-06-06 04:32:22 -04:00
}
}
2024-02-08 09:20:01 -05:00
cfm . log . Debug ( fmt . Sprintf ( "Measurements: %#v\n" , fetchedMeasurements ) )
2023-06-06 04:32:22 -04:00
2024-02-08 09:20:01 -05:00
cfm . log . Debug ( "Updating measurements in configuration" )
2022-08-01 03:37:05 -04:00
conf . UpdateMeasurements ( fetchedMeasurements )
2023-08-04 07:53:51 -04:00
if err := fileHandler . WriteYAML ( constants . ConfigFilename , conf , file . OptOverwrite ) ; err != nil {
2022-08-01 03:37:05 -04:00
return err
}
2024-02-08 09:20:01 -05:00
cfm . log . Debug ( fmt . Sprintf ( "Configuration written to %s" , cfm . flags . pathPrefixer . PrefixPrintablePath ( constants . ConfigFilename ) ) )
2023-06-19 10:51:39 -04:00
cmd . Print ( "Successfully fetched measurements and updated Configuration\n" )
2022-08-01 03:37:05 -04:00
return 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
2023-10-16 09:05:29 -04:00
// parseURLFlag checks that flag can be parsed as URL.
// If no value was provided for flag, nil is returned.
func parseURLFlag ( flags * pflag . FlagSet , flag string ) ( * url . URL , error ) {
rawURL , err := flags . GetString ( flag )
if err != nil {
return nil , fmt . Errorf ( "getting '%s' flag: %w" , flag , err )
}
if rawURL != "" {
return url . Parse ( rawURL )
}
return nil , nil
}
2023-05-26 11:49:46 -04:00
type rekorVerifier interface {
SearchByHash ( context . Context , string ) ( [ ] string , error )
VerifyEntry ( context . Context , string , string ) error
}