diff --git a/coordinator/internal/authority/credentials.go b/coordinator/internal/authority/credentials.go index 9597ad8c6..a9e4cbe16 100644 --- a/coordinator/internal/authority/credentials.go +++ b/coordinator/internal/authority/credentials.go @@ -10,6 +10,8 @@ import ( "log/slog" "net" "time" + "strconv" + "strings" "github.com/edgelesssys/contrast/internal/atls" "github.com/edgelesssys/contrast/internal/attestation" @@ -86,10 +88,11 @@ func (c *Credentials) ServerHandshake(rawConn net.Conn) (net.Conn, credentials.A return nil, nil, fmt.Errorf("generating SNP validation options: %w", err) } - for _, opt := range opts { + for i, opt := range opts { + name := "snp-" + strconv.Itoa(i) + "-" + strings.TrimLeft(opt.VerifyOpts.Product.Name.String(), "SEV_PRODUCT_") validator := snp.NewValidatorWithReportSetter(opt.VerifyOpts, opt.ValidateOpts, logger.NewWithAttrs(logger.NewNamed(c.logger, "validator"), map[string]string{"tee-type": "snp"}), - &authInfo) + &authInfo, name) validators = append(validators, validator) } @@ -98,9 +101,10 @@ func (c *Credentials) ServerHandshake(rawConn net.Conn) (net.Conn, credentials.A log.Error("Could not generate TDX validation options", "error", err) return nil, nil, fmt.Errorf("generating TDX validation options: %w", err) } - for _, opt := range tdxOpts { + for i, opt := range tdxOpts { + name := "tdx" + strconv.Itoa(i) validators = append(validators, tdx.NewValidatorWithReportSetter(&tdx.StaticValidateOptsGenerator{Opts: opt}, - logger.NewWithAttrs(logger.NewNamed(c.logger, "validator"), map[string]string{"tee-type": "tdx"}), &authInfo)) + logger.NewWithAttrs(logger.NewNamed(c.logger, "validator"), map[string]string{"tee-type": "tdx"}), &authInfo, name)) } serverCfg, err := atls.CreateAttestationServerTLSConfig(c.issuer, validators, c.attestationFailuresCounter) diff --git a/internal/atls/atls.go b/internal/atls/atls.go index 7196138a4..6cdbd683c 100644 --- a/internal/atls/atls.go +++ b/internal/atls/atls.go @@ -15,6 +15,7 @@ import ( "crypto/x509/pkix" "encoding/asn1" "encoding/base64" + "encoding/hex" "encoding/json" "errors" "fmt" @@ -94,6 +95,7 @@ type Issuer interface { type Validator interface { Getter Validate(attDoc []byte, nonce []byte, peerPublicKey []byte) error + Name() string } // getATLSConfigForClientFunc returns a config setup function that is called once for every client connecting to the server. @@ -233,7 +235,7 @@ func verifyEmbeddedReport(validators []Validator, cert *x509.Certificate, peerPu // We have a valid attestation document. Let's check it against all applicable validators. foundExtension = true - for _, validator := range validators { + for vId, validator := range validators { // Optimization: Skip the validator if it doesn't match the attestation type of the document. if !ex.Id.Equal(validator.OID()) { continue @@ -248,7 +250,13 @@ func verifyEmbeddedReport(validators []Validator, cert *x509.Certificate, peerPu return nil } // Otherwise, we'll keep track of the error and continue with the next validator. - retErr = errors.Join(retErr, fmt.Errorf("validator %s failed: %w", validator.OID(), validationErr)) + // The joined error should reveal the atls nonce once to maintain readability. Because the error is only revealed if all validators fail, + // we can implicitly include the nonce in the first validator error and the concatenate the other errors. + if vId == 0 { + retErr = errors.Join(retErr, fmt.Errorf("AtlsConnectionNonce: %s || validator %s failed: %w ||", hex.EncodeToString(nonce), validator.Name(), validationErr)) + } else { + retErr = errors.Join(retErr, fmt.Errorf(" validator %s failed: %w ||", validator.Name(), validationErr)) + } } } @@ -439,6 +447,11 @@ func (v FakeValidator) Validate(attDoc []byte, nonce []byte, _ []byte) error { return v.err } +// Name returns the name of the validator. +func (v *FakeValidator) Name() string { + return "" +} + // FakeAttestationDoc is a fake attestation document used for testing. type FakeAttestationDoc struct { UserData []byte diff --git a/internal/attestation/snp/validator.go b/internal/attestation/snp/validator.go index 86aa59fc0..63f1eec78 100644 --- a/internal/attestation/snp/validator.go +++ b/internal/attestation/snp/validator.go @@ -26,45 +26,49 @@ type Validator struct { validateOpts *validate.Options reportSetter attestation.ReportSetter logger *slog.Logger + name string } // NewValidator returns a new Validator. -func NewValidator(VerifyOpts *verify.Options, ValidateOpts *validate.Options, log *slog.Logger) *Validator { +func NewValidator(VerifyOpts *verify.Options, ValidateOpts *validate.Options, log *slog.Logger, name string) *Validator { return &Validator{ verifyOpts: VerifyOpts, validateOpts: ValidateOpts, logger: log, + name: name, } } // NewValidatorWithReportSetter returns a new Validator with a report setter. func NewValidatorWithReportSetter(VerifyOpts *verify.Options, ValidateOpts *validate.Options, - log *slog.Logger, reportSetter attestation.ReportSetter, + log *slog.Logger, reportSetter attestation.ReportSetter, name string, ) *Validator { return &Validator{ verifyOpts: VerifyOpts, validateOpts: ValidateOpts, reportSetter: reportSetter, logger: log, + name: name, } } -// OID returns the OID of the validator. +// OID returns the root OID for the raw SNP report extension used by the validator. func (v *Validator) OID() asn1.ObjectIdentifier { return oid.RawSNPReport } // Validate a TPM based attestation. func (v *Validator) Validate(attDocRaw []byte, nonce []byte, peerPublicKey []byte) (err error) { - v.logger.Info("Validate called", "nonce", hex.EncodeToString(nonce)) + v.logger.Info("Validate called", "name", v.name, "nonce", hex.EncodeToString(nonce)) defer func() { if err != nil { - v.logger.Error("Validation failed", "error", err) + v.logger.Debug("Validate failed", "name", v.name, "nonce", hex.EncodeToString(nonce), "error", err) } else { - v.logger.Info("Validation successful") + v.logger.Info("Validate succeeded", "name", v.name, "nonce", hex.EncodeToString(nonce)) } }() + // Parse the attestation document. attestationData := &sevsnp.Attestation{} @@ -99,6 +103,11 @@ func (v *Validator) Validate(attDocRaw []byte, nonce []byte, peerPublicKey []byt return nil } +// Name returns the name of the validator. +func (v *Validator) Name() string { + return v.name +} + type snpReport struct { report *sevsnp.Report } diff --git a/internal/attestation/tdx/validator.go b/internal/attestation/tdx/validator.go index 1fe33de77..4eae842a4 100644 --- a/internal/attestation/tdx/validator.go +++ b/internal/attestation/tdx/validator.go @@ -37,6 +37,7 @@ type Validator struct { validateOptsGen validateOptsGenerator reportSetter attestation.ReportSetter logger *slog.Logger + name string } type validateOptsGenerator interface { @@ -55,16 +56,17 @@ func (v *StaticValidateOptsGenerator) TDXValidateOpts(_ *tdx.QuoteV4) (*validate } // NewValidator returns a new Validator. -func NewValidator(optsGen validateOptsGenerator, log *slog.Logger) *Validator { +func NewValidator(optsGen validateOptsGenerator, log *slog.Logger, name string) *Validator { return &Validator{ validateOptsGen: optsGen, logger: log, + name: name, } } // NewValidatorWithReportSetter returns a new Validator with a report setter. -func NewValidatorWithReportSetter(optsGen validateOptsGenerator, log *slog.Logger, reportSetter attestation.ReportSetter) *Validator { - v := NewValidator(optsGen, log) +func NewValidatorWithReportSetter(optsGen validateOptsGenerator, log *slog.Logger, reportSetter attestation.ReportSetter, name string) *Validator { + v := NewValidator(optsGen, log, name) v.reportSetter = reportSetter return v } @@ -78,12 +80,12 @@ func (v *Validator) OID() asn1.ObjectIdentifier { func (v *Validator) Validate(attDocRaw []byte, nonce []byte, peerPublicKey []byte) (err error) { // TODO(freax13): Validate the memory integrity mode (logical vs cryptographic) in the provisioning certificate. - v.logger.Info("Validate called", "nonce", hex.EncodeToString(nonce)) + v.logger.Info("Validate called", "name", v.name, "nonce", hex.EncodeToString(nonce)) defer func() { if err != nil { - v.logger.Error("Validation failed", "error", err) + v.logger.Debug("Validate failed", "name", v.name, "nonce", hex.EncodeToString(nonce), "error", err) } else { - v.logger.Info("Validation successful") + v.logger.Info("Validate succeeded", "name", v.name, "nonce", hex.EncodeToString(nonce)) } }() @@ -137,6 +139,11 @@ func (v *Validator) Validate(attDocRaw []byte, nonce []byte, peerPublicKey []byt return nil } +// Name returns the name of the validator. +func (v *Validator) Name() string { + return v.name +} + func trustedRoots() (*x509.CertPool, error) { rootCerts := x509.NewCertPool() if ok := rootCerts.AppendCertsFromPEM(tdxRootCert); !ok { diff --git a/sdk/common.go b/sdk/common.go index b49f6c7a1..2ba6dcaf5 100644 --- a/sdk/common.go +++ b/sdk/common.go @@ -8,6 +8,8 @@ package sdk import ( "fmt" "log/slog" + "strconv" + "strings" "github.com/edgelesssys/contrast/internal/atls" "github.com/edgelesssys/contrast/internal/attestation/certcache" @@ -31,10 +33,11 @@ func ValidatorsFromManifest(kdsDir string, m *manifest.Manifest, log *slog.Logge if err != nil { return nil, fmt.Errorf("getting SNP validate options: %w", err) } - for _, opt := range opts { + for i, opt := range opts { opt.ValidateOpts.HostData = coordinatorPolicyChecksum + name := "snp-" + strconv.Itoa(i) + "-" + strings.TrimLeft(opt.VerifyOpts.Product.Name.String(), "SEV_PRODUCT_") validators = append(validators, snp.NewValidator(opt.VerifyOpts, opt.ValidateOpts, - logger.NewWithAttrs(logger.NewNamed(log, "validator"), map[string]string{"tee-type": "snp"}), + logger.NewWithAttrs(logger.NewNamed(log, "validator"), map[string]string{"tee-type": "snp"}), name, )) } @@ -44,9 +47,10 @@ func ValidatorsFromManifest(kdsDir string, m *manifest.Manifest, log *slog.Logge } var mrConfigID [48]byte copy(mrConfigID[:], coordinatorPolicyChecksum) - for _, opt := range tdxOpts { + for i, opt := range tdxOpts { + name := "tdx-" + strconv.Itoa(i) opt.TdQuoteBodyOptions.MrConfigID = mrConfigID[:] - validators = append(validators, tdx.NewValidator(&tdx.StaticValidateOptsGenerator{Opts: opt}, logger.NewWithAttrs(logger.NewNamed(log, "validator"), map[string]string{"tee-type": "tdx"}))) + validators = append(validators, tdx.NewValidator(&tdx.StaticValidateOptsGenerator{Opts: opt}, logger.NewWithAttrs(logger.NewNamed(log, "validator"), map[string]string{"tee-type": "tdx"}), name)) } return validators, nil