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-08-08 09:42:06 -04:00
"github.com/edgelesssys/constellation/v2/cli/internal/cmd/pathprefix"
2023-05-26 11:50:55 -04:00
"github.com/edgelesssys/constellation/v2/cli/internal/featureset"
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"
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"
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"
2023-08-01 10:48:13 -04:00
"github.com/edgelesssys/constellation/v2/internal/sigstore/keyselect"
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" )
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 {
measurementsURL * url . URL
signatureURL * url . URL
2023-06-06 04:32:22 -04:00
insecure bool
2023-01-31 05:45:31 -05:00
force bool
2023-08-08 09:42:06 -04:00
pf pathprefix . PathPrefixer
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-06-07 10:16:32 -04:00
fetcher := attestationconfigapi . NewFetcherWithClient ( http . DefaultClient )
2023-08-01 10:48:13 -04:00
return cfm . configFetchMeasurements ( cmd , sigstore . NewCosignVerifier , rekor , fileHandler , fetcher , http . DefaultClient )
2022-08-01 03:37:05 -04:00
}
2023-01-04 04:46:29 -05:00
func ( cfm * configFetchMeasurementsCmd ) configFetchMeasurements (
2023-08-01 10:48:13 -04:00
cmd * cobra . Command , newCosignVerifier cosignVerifierConstructor , rekor rekorVerifier ,
2023-06-07 10:16:32 -04:00
fileHandler file . Handler , fetcher attestationconfigapi . Fetcher , client * http . Client ,
2022-11-28 04:27:33 -05:00
) 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-08-16 09:38:40 -04:00
cfm . log . Debugf ( "Loading configuration file from %q" , flags . pf . PrefixPrintablePath ( constants . ConfigFilename ) )
2023-06-01 07:55:46 -04:00
2023-08-04 07:53:51 -04:00
conf , err := config . New ( fileHandler , constants . ConfigFilename , fetcher , 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
}
2023-06-01 07:55:46 -04:00
2023-08-01 10:48:13 -04:00
publicKey , err := keyselect . CosignPublicKeyForVersion ( imageVersion )
if err != nil {
return fmt . Errorf ( "getting public key: %w" , err )
}
cosign , err := newCosignVerifier ( publicKey )
if err != nil {
return fmt . Errorf ( "creating cosign verifier: %w" , err )
}
2022-11-15 09:40:49 -05:00
var fetchedMeasurements measurements . M
2023-06-06 04:32:22 -04:00
var hash string
if flags . insecure {
if err := fetchedMeasurements . FetchNoVerify (
ctx ,
client ,
flags . measurementsURL ,
imageVersion ,
conf . GetProvider ( ) ,
conf . GetAttestationConfig ( ) . GetVariant ( ) ,
) ; err != nil {
return fmt . Errorf ( "fetching measurements without verification: %w" , err )
}
cfm . log . Debugf ( "Fetched measurements without verification" )
} else {
hash , err = fetchedMeasurements . FetchAndVerify (
ctx ,
client ,
cosign ,
flags . measurementsURL ,
flags . signatureURL ,
imageVersion ,
conf . GetProvider ( ) ,
conf . GetAttestationConfig ( ) . GetVariant ( ) ,
)
if err != nil {
return fmt . Errorf ( "fetching and verifying measurements: %w" , err )
}
cfm . log . Debugf ( "Fetched and verified measurements, hash is %s" , hash )
2023-08-01 10:48:13 -04:00
if err := sigstore . VerifyWithRekor ( cmd . Context ( ) , publicKey , rekor , hash ) ; err != nil {
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!" )
}
cfm . log . Debugf ( "Verified measurements with Rekor" )
}
2023-08-04 03:43:32 -04:00
cfm . log . Debugf ( "Measurements:\n" , fetchedMeasurements )
2023-06-06 04:32:22 -04:00
cfm . log . Debugf ( "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
}
2023-08-16 09:38:40 -04:00
cfm . log . Debugf ( "Configuration written to %s" , flags . pf . 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
}
// 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 ) {
2023-08-08 09:42:06 -04:00
workDir , err := cmd . Flags ( ) . GetString ( "workspace" )
2023-08-04 07:53:51 -04:00
if err != nil {
return nil , fmt . Errorf ( "parsing workspace argument: %w" , err )
}
2023-01-04 04:46:29 -05:00
measurementsURL , err := cfm . parseURLFlag ( cmd , "url" )
2022-08-01 03:37:05 -04:00
if err != nil {
2023-08-04 07:53:51 -04:00
return nil , err
2022-08-01 03:37:05 -04:00
}
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 {
2023-08-04 07:53:51 -04:00
return nil , err
2022-08-01 03:37:05 -04:00
}
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
2023-06-06 04:32:22 -04:00
insecure , err := cmd . Flags ( ) . GetBool ( "insecure" )
if err != nil {
2023-08-04 07:53:51 -04:00
return nil , fmt . Errorf ( "parsing insecure argument: %w" , err )
2023-06-06 04:32:22 -04:00
}
cfm . log . Debugf ( "Insecure flag is %v" , insecure )
2023-01-31 05:45:31 -05:00
force , err := cmd . Flags ( ) . GetBool ( "force" )
if err != nil {
2023-08-04 07:53:51 -04:00
return nil , fmt . Errorf ( "parsing force argument: %w" , err )
2023-01-31 05:45:31 -05:00
}
2022-08-01 03:37:05 -04:00
return & fetchMeasurementsFlags {
measurementsURL : measurementsURL ,
signatureURL : measurementsSignatureURL ,
2023-06-06 04:32:22 -04:00
insecure : insecure ,
2023-01-31 05:45:31 -05:00
force : force ,
2023-08-08 09:42:06 -04:00
pf : pathprefix . New ( workDir ) ,
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
}
2023-08-01 10:48:13 -04:00
type cosignVerifierConstructor func ( [ ] byte ) ( sigstore . Verifier , error )