diff --git a/cli/internal/cmd/verify.go b/cli/internal/cmd/verify.go index e771c0b7fbf..31d3a63584e 100644 --- a/cli/internal/cmd/verify.go +++ b/cli/internal/cmd/verify.go @@ -32,6 +32,7 @@ import ( "github.com/edgelesssys/constellation/v2/internal/atls" "github.com/edgelesssys/constellation/v2/internal/attestation/measurements" "github.com/edgelesssys/constellation/v2/internal/attestation/vtpm" + "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/crypto" @@ -58,8 +59,7 @@ func NewVerifyCmd() *cobra.Command { RunE: runVerify, } cmd.Flags().String("cluster-id", "", "expected cluster identifier") - cmd.Flags().Bool("raw", false, "print raw attestation document") - cmd.Flags().StringP("output", "o", "", "print the attestation document in the output format {json}") + cmd.Flags().StringP("output", "o", "", "print the attestation document in the output format {json|raw}") cmd.Flags().StringP("node-endpoint", "e", "", "endpoint of the node to verify, passed as HOST[:PORT]") return cmd } @@ -80,20 +80,27 @@ func runVerify(cmd *cobra.Command, _ []string) error { dialer: dialer.New(nil, nil, &net.Dialer{}), log: log, } - formatterFactory := func(jsonOutput bool) attestationDocFormatter { - if jsonOutput { - return &jsonAttestationDocFormatter{log} + formatterFactory := func(output string, provider cloudprovider.Provider, log debugLog) (attestationDocFormatter, error) { + if output == "json" && provider != cloudprovider.Azure { + return nil, errors.New("json output is only supported for Azure") + } + switch output { + case "json": + return &jsonAttestationDocFormatter{log}, nil + case "raw": + return &rawAttestationDocFormatter{log}, nil + default: + return &defaultAttestationDocFormatter{log}, nil } - return &attestationDocFormatterImpl{log} } v := &verifyCmd{log: log} fetcher := attestationconfigapi.NewFetcher() return v.verify(cmd, fileHandler, verifyClient, formatterFactory, fetcher) } -type formatterFactory func(jsonOutput bool) attestationDocFormatter +type formatterFactory func(output string, provider cloudprovider.Provider, log debugLog) (attestationDocFormatter, error) -func (c *verifyCmd) verify(cmd *cobra.Command, fileHandler file.Handler, verifyClient verifyClient, formatter formatterFactory, configFetcher attestationconfigapi.Fetcher) error { +func (c *verifyCmd) verify(cmd *cobra.Command, fileHandler file.Handler, verifyClient verifyClient, factory formatterFactory, configFetcher attestationconfigapi.Fetcher) error { flags, err := c.parseVerifyFlags(cmd, fileHandler) if err != nil { return fmt.Errorf("parsing flags: %w", err) @@ -142,11 +149,14 @@ func (c *verifyCmd) verify(cmd *cobra.Command, fileHandler file.Handler, verifyC } // certificates are only available for Azure - attDocOutput, err := formatter(flags.jsonOutput).format( + formatter, err := factory(flags.output, conf.GetProvider(), c.log) + if err != nil { + return fmt.Errorf("creating formatter: %w", err) + } + attDocOutput, err := formatter.format( cmd.Context(), rawAttestationDoc, conf.Provider.Azure == nil, - flags.rawOutput, attConfig.GetMeasurements(), flags.maaURL, ) @@ -186,25 +196,11 @@ func (c *verifyCmd) parseVerifyFlags(cmd *cobra.Command, fileHandler file.Handle } c.log.Debugf("Flag 'force' set to %t", force) - raw, err := cmd.Flags().GetBool("raw") - if err != nil { - return verifyFlags{}, fmt.Errorf("parsing raw argument: %w", err) - } c.log.Debugf("Flag 'raw' set to %t", force) output, err := cmd.Flags().GetString("output") if err != nil { return verifyFlags{}, fmt.Errorf("parsing raw argument: %w", err) } - - var json bool - if output != "" { - if output != "json" { - return verifyFlags{}, fmt.Errorf("invalid output format %q, expected 'json'", output) - } - json = true - } - c.log.Debugf("Flag 'json' set to %t", force) - var idFile clusterid.File if err := fileHandler.ReadJSON(constants.ClusterIDsFilename, &idFile); err != nil && !errors.Is(err, afero.ErrFileNotFound) { return verifyFlags{}, fmt.Errorf("reading cluster ID file: %w", err) @@ -235,30 +231,25 @@ func (c *verifyCmd) parseVerifyFlags(cmd *cobra.Command, fileHandler file.Handle return verifyFlags{}, fmt.Errorf("validating endpoint argument: %w", err) } - if raw && json { - return verifyFlags{}, errors.New("cannot specify both --raw and --json") - } return verifyFlags{ - endpoint: endpoint, - pf: pf, - ownerID: ownerID, - clusterID: clusterID, - maaURL: idFile.AttestationURL, - rawOutput: raw, - jsonOutput: json, - force: force, + endpoint: endpoint, + pf: pf, + ownerID: ownerID, + clusterID: clusterID, + maaURL: idFile.AttestationURL, + output: output, + force: force, }, nil } type verifyFlags struct { - endpoint string - ownerID string - clusterID string - maaURL string - rawOutput bool - jsonOutput bool - force bool - pf pathprefix.PathPrefixer + endpoint string + ownerID string + clusterID string + maaURL string + output string + force bool + pf pathprefix.PathPrefixer } func addPortIfMissing(endpoint string, defaultPort int) (string, error) { @@ -279,10 +270,9 @@ func addPortIfMissing(endpoint string, defaultPort int) (string, error) { } // an attestationDocFormatter formats the attestation document. -// TODO(elchead): refactor the interface to be more generic (e.g. no rawOutput argument). type attestationDocFormatter interface { // format returns the raw or formatted attestation doc depending on the rawOutput argument. - format(ctx context.Context, docString string, PCRsOnly bool, rawOutput bool, expectedPCRs measurements.M, + format(ctx context.Context, docString string, PCRsOnly bool, expectedPCRs measurements.M, attestationServiceURL string) (string, error) } @@ -290,13 +280,10 @@ type jsonAttestationDocFormatter struct { log debugLog } -// format returns the raw or formatted attestation doc depending on the rawOutput argument. -func (f *jsonAttestationDocFormatter) format(ctx context.Context, docString string, PCRsOnly bool, - _ bool, _ measurements.M, attestationServiceURL string, +// format returns the json formatted attestation doc. +func (f *jsonAttestationDocFormatter) format(ctx context.Context, docString string, _ bool, + _ measurements.M, attestationServiceURL string, ) (string, error) { - if PCRsOnly { - return "", fmt.Errorf("JSON output is currently only supported for Azure") - } instanceInfo, err := extractAzureInstanceInfo(docString) if err != nil { return "", fmt.Errorf("unmarshal instance info: %w", err) @@ -330,20 +317,30 @@ func (f *jsonAttestationDocFormatter) format(ctx context.Context, docString stri return string(jsonBytes), err } -type attestationDocFormatterImpl struct { +type rawAttestationDocFormatter struct { log debugLog } -// format returns the raw or formatted attestation doc depending on the rawOutput argument. -func (f *attestationDocFormatterImpl) format(ctx context.Context, docString string, PCRsOnly bool, - rawOutput bool, expectedPCRs measurements.M, attestationServiceURL string, +// format returns the raw attestation doc. +func (f *rawAttestationDocFormatter) format(_ context.Context, docString string, _ bool, + _ measurements.M, _ string, +) (string, error) { + b := &strings.Builder{} + b.WriteString("Attestation Document:\n") + b.WriteString(fmt.Sprintf("%s\n", docString)) + return b.String(), nil +} + +type defaultAttestationDocFormatter struct { + log debugLog +} + +// format returns the formatted attestation doc. +func (f *defaultAttestationDocFormatter) format(ctx context.Context, docString string, PCRsOnly bool, + expectedPCRs measurements.M, attestationServiceURL string, ) (string, error) { b := &strings.Builder{} b.WriteString("Attestation Document:\n") - if rawOutput { - b.WriteString(fmt.Sprintf("%s\n", docString)) - return b.String(), nil - } var doc attestationDoc if err := json.Unmarshal([]byte(docString), &doc); err != nil { @@ -386,7 +383,7 @@ func (f *attestationDocFormatterImpl) format(ctx context.Context, docString stri } // parseCerts parses the PEM certificates and writes their details to the output builder. -func (f *attestationDocFormatterImpl) parseCerts(b *strings.Builder, certTypeName string, cert []byte) error { +func (f *defaultAttestationDocFormatter) parseCerts(b *strings.Builder, certTypeName string, cert []byte) error { newlinesTrimmed := strings.TrimSpace(string(cert)) formattedCert := strings.ReplaceAll(newlinesTrimmed, "\n", "\n\t\t") + "\n" b.WriteString(fmt.Sprintf("\tRaw %s:\n\t\t%s", certTypeName, formattedCert)) @@ -451,7 +448,7 @@ func (f *attestationDocFormatterImpl) parseCerts(b *strings.Builder, certTypeNam } // parseQuotes parses the base64-encoded quotes and writes their details to the output builder. -func (f *attestationDocFormatterImpl) parseQuotes(b *strings.Builder, quotes []*tpmProto.Quote, expectedPCRs measurements.M) error { +func (f *defaultAttestationDocFormatter) parseQuotes(b *strings.Builder, quotes []*tpmProto.Quote, expectedPCRs measurements.M) error { writeIndentfln(b, 1, "Quote:") var pcrNumbers []uint32 @@ -478,7 +475,7 @@ func (f *attestationDocFormatterImpl) parseQuotes(b *strings.Builder, quotes []* return nil } -func (f *attestationDocFormatterImpl) buildSNPReport(b *strings.Builder, report verify.SNPReport) { +func (f *defaultAttestationDocFormatter) buildSNPReport(b *strings.Builder, report verify.SNPReport) { writeTCB := func(tcb verify.TCBVersion) { writeIndentfln(b, 3, "Secure Processor bootloader SVN: %d", tcb.Bootloader) writeIndentfln(b, 3, "Secure Processor operating system SVN: %d", tcb.TEE) diff --git a/cli/internal/cmd/verify_test.go b/cli/internal/cmd/verify_test.go index c9d53bdea29..508b0f23859 100644 --- a/cli/internal/cmd/verify_test.go +++ b/cli/internal/cmd/verify_test.go @@ -185,8 +185,8 @@ func TestVerify(t *testing.T) { } v := &verifyCmd{log: logger.NewTest(t)} - formatterFac := func(_ bool) attestationDocFormatter { - return tc.formatter + formatterFac := func(_ string, _ cloudprovider.Provider, _ debugLog) (attestationDocFormatter, error) { + return tc.formatter, nil } err := v.verify(cmd, fileHandler, tc.protoClient, formatterFac, stubAttestationFetcher{}) if tc.wantErr { @@ -204,19 +204,19 @@ type stubAttDocFormatter struct { formatErr error } -func (f *stubAttDocFormatter) format(_ context.Context, _ string, _ bool, _ bool, _ measurements.M, _ string) (string, error) { +func (f *stubAttDocFormatter) format(_ context.Context, _ string, _ bool, _ measurements.M, _ string) (string, error) { return "", f.formatErr } func TestFormat(t *testing.T) { - formatter := func() *attestationDocFormatterImpl { - return &attestationDocFormatterImpl{ + formatter := func() *defaultAttestationDocFormatter { + return &defaultAttestationDocFormatter{ log: logger.NewTest(t), } } testCases := map[string]struct { - formatter *attestationDocFormatterImpl + formatter *defaultAttestationDocFormatter doc string wantErr bool }{ @@ -229,7 +229,7 @@ func TestFormat(t *testing.T) { for name, tc := range testCases { t.Run(name, func(t *testing.T) { - _, err := tc.formatter.format(context.Background(), tc.doc, false, false, nil, "") + _, err := tc.formatter.format(context.Background(), tc.doc, false, nil, "") if tc.wantErr { assert.Error(t, err) } else { @@ -301,7 +301,7 @@ F/SjRih31+SAtWb42jueAA== assert := assert.New(t) b := &strings.Builder{} - formatter := &attestationDocFormatterImpl{ + formatter := &defaultAttestationDocFormatter{ log: logger.NewTest(t), } err := formatter.parseCerts(b, "Some Cert", tc.cert) @@ -547,7 +547,7 @@ func TestParseQuotes(t *testing.T) { assert := assert.New(t) b := &strings.Builder{} - parser := &attestationDocFormatterImpl{} + parser := &defaultAttestationDocFormatter{} err := parser.parseQuotes(b, tc.quotes, tc.expectedPCRs) if tc.wantErr {