Skip to content

Commit daaa775

Browse files
cli: enable JSON output for constellation verify on Azure TDX (#3164)
* Remove formatter factory * Enable `constellation verify` with JSON output for Azure TDX --------- Signed-off-by: Daniel Weiße <dw@edgeless.systems>
1 parent b3fcdc9 commit daaa775

File tree

7 files changed

+90
-157
lines changed

7 files changed

+90
-157
lines changed

cli/internal/cmd/BUILD.bazel

+3
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,9 @@ go_library(
111111
"@io_k8s_sigs_yaml//:yaml",
112112
"@org_golang_x_mod//semver",
113113
"@org_golang_google_grpc//:grpc",
114+
"@com_github_google_go_tdx_guest//abi",
115+
"@com_github_google_go_tdx_guest//proto/tdx",
116+
"//internal/attestation/azure/tdx",
114117
] + select({
115118
"@io_bazel_rules_go//go/platform:android_amd64": [
116119
"@org_golang_x_sys//unix",

cli/internal/cmd/verify.go

+72-101
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,9 @@ import (
2121
"strconv"
2222
"strings"
2323

24-
tpmProto "github.com/google/go-tpm-tools/proto/tpm"
25-
2624
"github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi"
2725
"github.com/edgelesssys/constellation/v2/internal/atls"
26+
azuretdx "github.com/edgelesssys/constellation/v2/internal/attestation/azure/tdx"
2827
"github.com/edgelesssys/constellation/v2/internal/attestation/choose"
2928
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
3029
"github.com/edgelesssys/constellation/v2/internal/attestation/snp"
@@ -38,6 +37,10 @@ import (
3837
"github.com/edgelesssys/constellation/v2/internal/grpc/dialer"
3938
"github.com/edgelesssys/constellation/v2/internal/verify"
4039
"github.com/edgelesssys/constellation/v2/verify/verifyproto"
40+
41+
"github.com/google/go-tdx-guest/abi"
42+
"github.com/google/go-tdx-guest/proto/tdx"
43+
tpmProto "github.com/google/go-tpm-tools/proto/tpm"
4144
"github.com/spf13/afero"
4245
"github.com/spf13/cobra"
4346
"github.com/spf13/pflag"
@@ -106,24 +109,7 @@ func runVerify(cmd *cobra.Command, _ []string) error {
106109
dialer: dialer.New(nil, nil, &net.Dialer{}),
107110
log: log,
108111
}
109-
formatterFactory := func(output string, attestation variant.Variant, log debugLog) (attestationDocFormatter, error) {
110-
if output == "json" &&
111-
(!attestation.Equal(variant.AzureSEVSNP{}) &&
112-
!attestation.Equal(variant.AWSSEVSNP{}) &&
113-
!attestation.Equal(variant.GCPSEVSNP{})) {
114-
return nil, errors.New("json output is only supported for SEV-SNP")
115-
}
116-
switch output {
117-
case "json":
118-
return &jsonAttestationDocFormatter{log}, nil
119-
case "raw":
120-
return &rawAttestationDocFormatter{log}, nil
121-
case "":
122-
return &defaultAttestationDocFormatter{log}, nil
123-
default:
124-
return nil, fmt.Errorf("invalid output value for formatter: %s", output)
125-
}
126-
}
112+
127113
v := &verifyCmd{
128114
fileHandler: fileHandler,
129115
log: log,
@@ -132,13 +118,12 @@ func runVerify(cmd *cobra.Command, _ []string) error {
132118
return err
133119
}
134120
v.log.Debug("Using flags", "clusterID", v.flags.clusterID, "endpoint", v.flags.endpoint, "ownerID", v.flags.ownerID)
121+
135122
fetcher := attestationconfigapi.NewFetcher()
136-
return v.verify(cmd, verifyClient, formatterFactory, fetcher)
123+
return v.verify(cmd, verifyClient, fetcher)
137124
}
138125

139-
type formatterFactory func(output string, attestation variant.Variant, log debugLog) (attestationDocFormatter, error)
140-
141-
func (c *verifyCmd) verify(cmd *cobra.Command, verifyClient verifyClient, factory formatterFactory, configFetcher attestationconfigapi.Fetcher) error {
126+
func (c *verifyCmd) verify(cmd *cobra.Command, verifyClient verifyClient, configFetcher attestationconfigapi.Fetcher) error {
142127
c.log.Debug(fmt.Sprintf("Loading configuration file from %q", c.flags.pathPrefixer.PrefixPrintablePath(constants.ConfigFilename)))
143128
conf, err := config.New(c.fileHandler, constants.ConfigFilename, configFetcher, c.flags.force)
144129
var configValidationErr *config.ValidationError
@@ -202,20 +187,21 @@ func (c *verifyCmd) verify(cmd *cobra.Command, verifyClient verifyClient, factor
202187
return fmt.Errorf("verifying: %w", err)
203188
}
204189

205-
// certificates are only available for Azure SEV-SNP and AWS SEV-SNP
206-
formatter, err := factory(c.flags.output, conf.GetAttestationConfig().GetVariant(), c.log)
207-
if err != nil {
208-
return fmt.Errorf("creating formatter: %w", err)
190+
var attDocOutput string
191+
switch c.flags.output {
192+
case "json":
193+
attDocOutput, err = formatJSON(cmd.Context(), rawAttestationDoc, attConfig, c.log)
194+
case "raw":
195+
attDocOutput = fmt.Sprintf("Attestation Document:\n%s\n", rawAttestationDoc)
196+
case "":
197+
attDocOutput, err = formatDefault(cmd.Context(), rawAttestationDoc, attConfig, c.log)
198+
default:
199+
return fmt.Errorf("invalid output value for formatter: %s", c.flags.output)
209200
}
210-
attDocOutput, err := formatter.format(
211-
cmd.Context(),
212-
rawAttestationDoc,
213-
(!attConfig.GetVariant().Equal(variant.AzureSEVSNP{}) && !attConfig.GetVariant().Equal(variant.AWSSEVSNP{})),
214-
attConfig,
215-
)
216201
if err != nil {
217202
return fmt.Errorf("printing attestation document: %w", err)
218203
}
204+
219205
cmd.Println(attDocOutput)
220206
cmd.PrintErrln("Verification OK")
221207

@@ -255,82 +241,92 @@ func (c *verifyCmd) validateEndpointFlag(cmd *cobra.Command, stateFile *state.St
255241
return endpoint, nil
256242
}
257243

258-
// an attestationDocFormatter formats the attestation document.
259-
type attestationDocFormatter interface {
260-
// format returns the raw or formatted attestation doc depending on the rawOutput argument.
261-
format(ctx context.Context, docString string, PCRsOnly bool, attestationCfg config.AttestationCfg) (string, error)
262-
}
263-
264-
type jsonAttestationDocFormatter struct {
265-
log debugLog
266-
}
267-
268-
// format returns the json formatted attestation doc.
269-
func (f *jsonAttestationDocFormatter) format(ctx context.Context, docString string, _ bool,
270-
attestationCfg config.AttestationCfg,
244+
// formatJSON returns the json formatted attestation doc.
245+
func formatJSON(ctx context.Context, docString string, attestationCfg config.AttestationCfg, log debugLog,
271246
) (string, error) {
272-
var doc attestationDoc
247+
var doc vtpm.AttestationDocument
273248
if err := json.Unmarshal([]byte(docString), &doc); err != nil {
274-
return "", fmt.Errorf("unmarshal attestation document: %w", err)
249+
return "", fmt.Errorf("unmarshalling attestation document: %w", err)
275250
}
276251

277-
instanceInfo, err := extractInstanceInfo(doc)
278-
if err != nil {
252+
switch attestationCfg.GetVariant() {
253+
case variant.AWSSEVSNP{}, variant.AzureSEVSNP{}, variant.GCPSEVSNP{}:
254+
return snpFormatJSON(ctx, doc.InstanceInfo, attestationCfg, log)
255+
case variant.AzureTDX{}:
256+
return tdxFormatJSON(doc.InstanceInfo, attestationCfg)
257+
default:
258+
return "", fmt.Errorf("json output is not supported for variant %s", attestationCfg.GetVariant())
259+
}
260+
}
261+
262+
func snpFormatJSON(ctx context.Context, instanceInfoRaw []byte, attestationCfg config.AttestationCfg, log debugLog,
263+
) (string, error) {
264+
var instanceInfo snp.InstanceInfo
265+
if err := json.Unmarshal(instanceInfoRaw, &instanceInfo); err != nil {
279266
return "", fmt.Errorf("unmarshalling instance info: %w", err)
280267
}
281-
report, err := verify.NewReport(ctx, instanceInfo, attestationCfg, f.log)
268+
report, err := verify.NewReport(ctx, instanceInfo, attestationCfg, log)
282269
if err != nil {
283270
return "", fmt.Errorf("parsing SNP report: %w", err)
284271
}
285272

286273
jsonBytes, err := json.Marshal(report)
287-
288274
return string(jsonBytes), err
289275
}
290276

291-
type rawAttestationDocFormatter struct {
292-
log debugLog
293-
}
277+
func tdxFormatJSON(instanceInfoRaw []byte, attestationCfg config.AttestationCfg) (string, error) {
278+
var rawQuote []byte
294279

295-
// format returns the raw attestation doc.
296-
func (f *rawAttestationDocFormatter) format(_ context.Context, docString string, _ bool,
297-
_ config.AttestationCfg,
298-
) (string, error) {
299-
b := &strings.Builder{}
300-
b.WriteString("Attestation Document:\n")
301-
b.WriteString(fmt.Sprintf("%s\n", docString))
302-
return b.String(), nil
303-
}
280+
if attestationCfg.GetVariant().Equal(variant.AzureTDX{}) {
281+
var instanceInfo azuretdx.InstanceInfo
282+
if err := json.Unmarshal(instanceInfoRaw, &instanceInfo); err != nil {
283+
return "", fmt.Errorf("unmarshalling instance info: %w", err)
284+
}
285+
rawQuote = instanceInfo.AttestationReport
286+
}
287+
288+
tdxQuote, err := abi.QuoteToProto(rawQuote)
289+
if err != nil {
290+
return "", fmt.Errorf("converting quote to proto: %w", err)
291+
}
292+
quote, ok := tdxQuote.(*tdx.QuoteV4)
293+
if !ok {
294+
return "", fmt.Errorf("unexpected quote type: %T", tdxQuote)
295+
}
304296

305-
type defaultAttestationDocFormatter struct {
306-
log debugLog
297+
quoteJSON, err := json.Marshal(quote)
298+
return string(quoteJSON), err
307299
}
308300

309301
// format returns the formatted attestation doc.
310-
func (f *defaultAttestationDocFormatter) format(ctx context.Context, docString string, PCRsOnly bool,
311-
attestationCfg config.AttestationCfg,
302+
func formatDefault(ctx context.Context, docString string, attestationCfg config.AttestationCfg, log debugLog,
312303
) (string, error) {
313304
b := &strings.Builder{}
314305
b.WriteString("Attestation Document:\n")
315306

316-
var doc attestationDoc
307+
var doc vtpm.AttestationDocument
317308
if err := json.Unmarshal([]byte(docString), &doc); err != nil {
318309
return "", fmt.Errorf("unmarshal attestation document: %w", err)
319310
}
320311

321-
if err := f.parseQuotes(b, doc.Attestation.Quotes, attestationCfg.GetMeasurements()); err != nil {
312+
if err := parseQuotes(b, doc.Attestation.Quotes, attestationCfg.GetMeasurements()); err != nil {
322313
return "", fmt.Errorf("parse quote: %w", err)
323314
}
324-
if PCRsOnly {
315+
316+
// If we have a non SNP variant, print only the PCRs
317+
if !(attestationCfg.GetVariant().Equal(variant.AzureSEVSNP{}) ||
318+
attestationCfg.GetVariant().Equal(variant.AWSSEVSNP{}) ||
319+
attestationCfg.GetVariant().Equal(variant.GCPSEVSNP{})) {
325320
return b.String(), nil
326321
}
327322

328-
instanceInfo, err := extractInstanceInfo(doc)
329-
if err != nil {
323+
// SNP reports contain extra information that we can print
324+
var instanceInfo snp.InstanceInfo
325+
if err := json.Unmarshal(doc.InstanceInfo, &instanceInfo); err != nil {
330326
return "", fmt.Errorf("unmarshalling instance info: %w", err)
331327
}
332328

333-
report, err := verify.NewReport(ctx, instanceInfo, attestationCfg, f.log)
329+
report, err := verify.NewReport(ctx, instanceInfo, attestationCfg, log)
334330
if err != nil {
335331
return "", fmt.Errorf("parsing SNP report: %w", err)
336332
}
@@ -339,7 +335,7 @@ func (f *defaultAttestationDocFormatter) format(ctx context.Context, docString s
339335
}
340336

341337
// parseQuotes parses the base64-encoded quotes and writes their details to the output builder.
342-
func (f *defaultAttestationDocFormatter) parseQuotes(b *strings.Builder, quotes []*tpmProto.Quote, expectedPCRs measurements.M) error {
338+
func parseQuotes(b *strings.Builder, quotes []*tpmProto.Quote, expectedPCRs measurements.M) error {
343339
writeIndentfln(b, 1, "Quote:")
344340

345341
var pcrNumbers []uint32
@@ -366,18 +362,6 @@ func (f *defaultAttestationDocFormatter) parseQuotes(b *strings.Builder, quotes
366362
return nil
367363
}
368364

369-
// attestationDoc is the attestation document returned by the verifier.
370-
type attestationDoc struct {
371-
Attestation struct {
372-
AkPub string `json:"ak_pub"`
373-
Quotes []*tpmProto.Quote `json:"quotes"`
374-
EventLog string `json:"event_log"`
375-
TeeAttestation interface{} `json:"TeeAttestation"`
376-
} `json:"Attestation"`
377-
InstanceInfo string `json:"InstanceInfo"`
378-
UserData string `json:"UserData"`
379-
}
380-
381365
type constellationVerifier struct {
382366
dialer grpcInsecureDialer
383367
log debugLog
@@ -432,19 +416,6 @@ func writeIndentfln(b *strings.Builder, indentLvl int, format string, args ...an
432416
b.WriteString(fmt.Sprintf(format+"\n", args...))
433417
}
434418

435-
func extractInstanceInfo(doc attestationDoc) (snp.InstanceInfo, error) {
436-
instanceInfoString, err := base64.StdEncoding.DecodeString(doc.InstanceInfo)
437-
if err != nil {
438-
return snp.InstanceInfo{}, fmt.Errorf("decode instance info: %w", err)
439-
}
440-
441-
var instanceInfo snp.InstanceInfo
442-
if err := json.Unmarshal(instanceInfoString, &instanceInfo); err != nil {
443-
return snp.InstanceInfo{}, fmt.Errorf("unmarshal instance info: %w", err)
444-
}
445-
return instanceInfo, nil
446-
}
447-
448419
func addPortIfMissing(endpoint string, defaultPort int) (string, error) {
449420
if endpoint == "" {
450421
return "", errors.New("endpoint is empty")

0 commit comments

Comments
 (0)