Skip to content

Commit

Permalink
treewide: allow multiple validators
Browse files Browse the repository at this point in the history
This changes the attestation (as of now, only SEV-SNP) to be passed
multiple validators. The aTLS code already handles multiple validators,
but the code previously passed only one. This way, attestation will now
work by being handed a list of validators, and returning success as soon
as one can successfully validate a report. Furthermore, the
`atls.NoValidator` is now obsolete, and semantically represented by
passing an empty list of validators.
  • Loading branch information
msanft committed Aug 9, 2024
1 parent ccce725 commit 590bf28
Show file tree
Hide file tree
Showing 15 changed files with 247 additions and 200 deletions.
20 changes: 12 additions & 8 deletions cli/cmd/recover.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,17 +81,21 @@ func runRecover(cmd *cobra.Command, _ []string) error {
return fmt.Errorf("getting cache dir: %w", err)
}
log.Debug("Using KDS cache dir", "dir", kdsDir)
kdsCache := fsstore.New(kdsDir, log.WithGroup("kds-cache"))
kdsGetter := snp.NewCachedHTTPSGetter(kdsCache, snp.NeverGCTicker, log.WithGroup("kds-getter"))

validateOptsGen, err := newCoordinatorValidateOptsGen(m, flags.policy)
optsGens, err := m.SNPValidateOpts()
if err != nil {
return fmt.Errorf("generating validate opts: %w", err)
return fmt.Errorf("getting AKS validate options: %w", err)
}
kdsCache := fsstore.New(kdsDir, log.WithGroup("kds-cache"))
kdsGetter := snp.NewCachedHTTPSGetter(kdsCache, snp.NeverGCTicker, log.WithGroup("kds-getter"))
validator := snp.NewValidator(validateOptsGen, kdsGetter,
logger.NewWithAttrs(logger.NewNamed(log, "validator"), map[string]string{"tee-type": "snp"}),
)
dialer := dialer.NewWithKey(atls.NoIssuer, validator, &net.Dialer{}, workloadOwnerKey)

var validators []atls.Validator
for _, gen := range optsGens {
validators = append(validators, snp.NewValidator(gen.WithStaticHostData(flags.policy), kdsGetter,
logger.NewWithAttrs(logger.NewNamed(log, "validator"), map[string]string{"tee-type": "snp"}),
))
}
dialer := dialer.NewWithKey(atls.NoIssuer, validators, &net.Dialer{}, workloadOwnerKey)

log.Debug("Dialing coordinator", "endpoint", flags.coordinator)
conn, err := dialer.Dial(cmd.Context(), flags.coordinator)
Expand Down
20 changes: 12 additions & 8 deletions cli/cmd/set.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,17 +106,21 @@ func runSet(cmd *cobra.Command, args []string) error {
return fmt.Errorf("getting cache dir: %w", err)
}
log.Debug("Using KDS cache dir", "dir", kdsDir)
kdsCache := fsstore.New(kdsDir, log.WithGroup("kds-cache"))
kdsGetter := snp.NewCachedHTTPSGetter(kdsCache, snp.NeverGCTicker, log.WithGroup("kds-getter"))

validateOptsGen, err := newCoordinatorValidateOptsGen(m, flags.policy)
optsGens, err := m.SNPValidateOpts()
if err != nil {
return fmt.Errorf("generating validate opts: %w", err)
return fmt.Errorf("getting AKS validate options: %w", err)
}
kdsCache := fsstore.New(kdsDir, log.WithGroup("kds-cache"))
kdsGetter := snp.NewCachedHTTPSGetter(kdsCache, snp.NeverGCTicker, log.WithGroup("kds-getter"))
validator := snp.NewValidator(validateOptsGen, kdsGetter,
logger.NewWithAttrs(logger.NewNamed(log, "validator"), map[string]string{"tee-type": "snp"}),
)
dialer := dialer.NewWithKey(atls.NoIssuer, validator, &net.Dialer{}, workloadOwnerKey)

var validators []atls.Validator
for _, gen := range optsGens {
validators = append(validators, snp.NewValidator(gen.WithStaticHostData(flags.policy), kdsGetter,
logger.NewWithAttrs(logger.NewNamed(log, "validator"), map[string]string{"tee-type": "snp"}),
))
}
dialer := dialer.NewWithKey(atls.NoIssuer, validators, &net.Dialer{}, workloadOwnerKey)

conn, err := dialer.Dial(cmd.Context(), flags.coordinator)
if err != nil {
Expand Down
31 changes: 12 additions & 19 deletions cli/cmd/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,17 +76,21 @@ func runVerify(cmd *cobra.Command, _ []string) error {
return fmt.Errorf("getting cache dir: %w", err)
}
log.Debug("Using KDS cache dir", "dir", kdsDir)
kdsCache := fsstore.New(kdsDir, log.WithGroup("kds-cache"))
kdsGetter := snp.NewCachedHTTPSGetter(kdsCache, snp.NeverGCTicker, log.WithGroup("kds-getter"))

validateOptsGen, err := newCoordinatorValidateOptsGen(m, flags.policy)
optsGens, err := m.SNPValidateOpts()
if err != nil {
return fmt.Errorf("generating validate opts: %w", err)
return fmt.Errorf("getting AKS validate options: %w", err)
}
kdsCache := fsstore.New(kdsDir, log.WithGroup("kds-cache"))
kdsGetter := snp.NewCachedHTTPSGetter(kdsCache, snp.NeverGCTicker, log.WithGroup("kds-getter"))
validator := snp.NewValidator(validateOptsGen, kdsGetter,
logger.NewWithAttrs(logger.NewNamed(log, "validator"), map[string]string{"tee-type": "snp"}),
)
dialer := dialer.New(atls.NoIssuer, validator, &net.Dialer{})

var validators []atls.Validator
for _, gen := range optsGens {
validators = append(validators, snp.NewValidator(gen.WithStaticHostData(flags.policy), kdsGetter,
logger.NewWithAttrs(logger.NewNamed(log, "validator"), map[string]string{"tee-type": "snp"}),
))
}
dialer := dialer.New(atls.NoIssuer, validators, &net.Dialer{})

log.Debug("Dialing coordinator", "endpoint", flags.coordinator)
conn, err := dialer.Dial(cmd.Context(), flags.coordinator)
Expand Down Expand Up @@ -174,17 +178,6 @@ func parseVerifyFlags(cmd *cobra.Command) (*verifyFlags, error) {
}, nil
}

func newCoordinatorValidateOptsGen(mnfst manifest.Manifest, hostData []byte) (*snp.StaticValidateOptsGenerator, error) {
validateOpts, err := mnfst.AKSValidateOpts()
if err != nil {
return nil, err
}
validateOpts.HostData = hostData
return &snp.StaticValidateOptsGenerator{
Opts: validateOpts,
}, nil
}

func writeFilelist(dir string, filelist map[string][]byte) error {
if dir != "" {
if err := os.MkdirAll(dir, 0o755); err != nil {
Expand Down
26 changes: 7 additions & 19 deletions coordinator/internal/authority/authority.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ import (
"github.com/edgelesssys/contrast/internal/ca"
"github.com/edgelesssys/contrast/internal/manifest"
"github.com/edgelesssys/contrast/internal/userapi"
"github.com/google/go-sev-guest/proto/sevsnp"
"github.com/google/go-sev-guest/validate"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)
Expand Down Expand Up @@ -48,7 +46,8 @@ type Authority struct {
}

type metrics struct {
manifestGeneration prometheus.Gauge
manifestGeneration prometheus.Gauge
attestationFailures prometheus.Counter
}

// New creates a new Authority instance.
Expand All @@ -65,6 +64,11 @@ func New(hist *history.History, reg *prometheus.Registry, log *slog.Logger) *Aut
logger: log.WithGroup("mesh-authority"),
metrics: metrics{
manifestGeneration: manifestGeneration,
attestationFailures: promauto.With(reg).NewCounter(prometheus.CounterOpts{
Subsystem: "contrast_meshapi",
Name: "attestation_failures_total",
Help: "Number of attestation failures from workloads to the Coordinator.",
}),
},
}
}
Expand Down Expand Up @@ -179,19 +183,3 @@ type State struct {
latest *history.LatestTransition
generation int
}

// SNPValidateOpts returns SNP validation options from reference values.
//
// It also ensures that the policy hash in the report's HOSTDATA is allowed by the current
// manifest.
// TODO(msanft): make the manifest authoritative and allow other types of reference values.
func (s *State) SNPValidateOpts(report *sevsnp.Report) (*validate.Options, error) {
mnfst := s.Manifest

hostData := manifest.NewHexString(report.HostData)
if _, ok := mnfst.Policies[hostData]; !ok {
return nil, fmt.Errorf("hostdata %s not found in manifest", hostData)
}

return mnfst.AKSValidateOpts()
}
12 changes: 8 additions & 4 deletions coordinator/internal/authority/authority_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,20 @@ func TestSNPValidateOpts(t *testing.T) {
_, err := a.SetManifest(context.Background(), req)
require.NoError(err)

opts, err := a.state.Load().SNPValidateOpts(report)
gens, err := a.state.Load().Manifest.SNPValidateOpts()
require.NoError(err)
require.NotNil(opts)
require.NotNil(gens)

// Change to unknown policy hash in HostData.
report.HostData[0]++

opts, err = a.state.Load().SNPValidateOpts(report)
gens, err = a.state.Load().Manifest.SNPValidateOpts()
require.NoError(err)
require.NotNil(gens)

gen := gens[0].WithReportHostData()
_, err = gen.SNPValidateOpts(report)
require.Error(err)
require.Nil(opts)
}

// TODO(burgerdev): test ValidateCallback and GetCertBundle
Expand Down
16 changes: 12 additions & 4 deletions coordinator/internal/authority/credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,19 @@ func (c *Credentials) ServerHandshake(rawConn net.Conn) (net.Conn, credentials.A

authInfo := AuthInfo{State: state}

validator := snp.NewValidatorWithCallbacks(state, c.kdsGetter,
logger.NewWithAttrs(logger.NewNamed(c.logger, "validator"), map[string]string{"tee-type": "snp"}),
c.attestationFailuresCounter, &authInfo)
optsGens, err := state.Manifest.SNPValidateOpts()
if err != nil {
return nil, nil, fmt.Errorf("generating SNP validation options: %w", err)
}

serverCfg, err := atls.CreateAttestationServerTLSConfig(c.issuer, []atls.Validator{validator})
var validators []atls.Validator
for _, gen := range optsGens {
validator := snp.NewValidatorWithCallbacks(gen.WithReportHostData(), c.kdsGetter,
logger.NewWithAttrs(logger.NewNamed(c.logger, "validator"), map[string]string{"tee-type": "snp"}),
c.attestationFailuresCounter, &authInfo)
validators = append(validators, validator)
}
serverCfg, err := atls.CreateAttestationServerTLSConfig(c.issuer, validators)
if err != nil {
return nil, nil, err
}
Expand Down
4 changes: 3 additions & 1 deletion initializer/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@ func run() (retErr error) {
}

requestCert := func() (*meshapi.NewMeshCertResponse, error) {
dial := dialer.NewWithKey(issuer, atls.NoValidator, &net.Dialer{}, privKey)
// Supply an empty list of validators, as the coordinator does not need to be
// validated by the initializer.
dial := dialer.NewWithKey(issuer, []atls.Validator{}, &net.Dialer{}, privKey)
conn, err := dial.Dial(ctx, net.JoinHostPort(coordinatorHostname, meshapi.Port))
if err != nil {
return nil, fmt.Errorf("dialing: %w", err)
Expand Down
2 changes: 0 additions & 2 deletions internal/atls/atls.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@ import (
const attestationTimeout = 30 * time.Second

var (
// NoValidator skips validation of the server's attestation document.
NoValidator Validator
// NoIssuer skips embedding the client's attestation document.
NoIssuer Issuer

Expand Down
8 changes: 4 additions & 4 deletions internal/attestation/snp/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,13 @@ type validateOptsGenerator interface {
SNPValidateOpts(report *sevsnp.Report) (*validate.Options, error)
}

// StaticValidateOptsGenerator returns validate.Options generator that returns
// StaticValidateOptsGenerator is a [validate.Options] generator that returns
// static validation options.
type StaticValidateOptsGenerator struct {
Opts *validate.Options
}

// SNPValidateOpts return the SNP validation options.
// SNPValidateOpts returns the SNP validation options.
func (v *StaticValidateOptsGenerator) SNPValidateOpts(_ *sevsnp.Report) (*validate.Options, error) {
return v.Opts, nil
}
Expand All @@ -65,13 +65,13 @@ func NewValidator(optsGen validateOptsGenerator, kdsGetter trust.HTTPSGetter, lo
}

// NewValidatorWithCallbacks returns a new Validator with callbacks.
func NewValidatorWithCallbacks(optsGen validateOptsGenerator, kdsGetter trust.HTTPSGetter, log *slog.Logger, attestataionFailures prometheus.Counter, callbacks ...validateCallbacker) *Validator {
func NewValidatorWithCallbacks(optsGen validateOptsGenerator, kdsGetter trust.HTTPSGetter, log *slog.Logger, attestationFailures prometheus.Counter, callbacks ...validateCallbacker) *Validator {
return &Validator{
validateOptsGen: optsGen,
callbackers: callbacks,
kdsGetter: kdsGetter,
logger: log,
metrics: metrics{attestationFailures: attestataionFailures},
metrics: metrics{attestationFailures: attestationFailures},
}
}

Expand Down
32 changes: 14 additions & 18 deletions internal/grpc/dialer/dialer.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,38 +18,34 @@ import (

// Dialer can open grpc client connections with different levels of ATLS encryption / verification.
type Dialer struct {
issuer atls.Issuer
validator atls.Validator
netDialer NetDialer
privKey *ecdsa.PrivateKey
issuer atls.Issuer
validators []atls.Validator
netDialer NetDialer
privKey *ecdsa.PrivateKey
}

// New creates a new Dialer.
func New(issuer atls.Issuer, validator atls.Validator, netDialer NetDialer) *Dialer {
func New(issuer atls.Issuer, validators []atls.Validator, netDialer NetDialer) *Dialer {
return &Dialer{
issuer: issuer,
validator: validator,
netDialer: netDialer,
issuer: issuer,
validators: validators,
netDialer: netDialer,
}
}

// NewWithKey creates a new Dialer with the given private key.
func NewWithKey(issuer atls.Issuer, validator atls.Validator, netDialer NetDialer, privKey *ecdsa.PrivateKey) *Dialer {
func NewWithKey(issuer atls.Issuer, validators []atls.Validator, netDialer NetDialer, privKey *ecdsa.PrivateKey) *Dialer {
return &Dialer{
issuer: issuer,
validator: validator,
netDialer: netDialer,
privKey: privKey,
issuer: issuer,
validators: validators,
netDialer: netDialer,
privKey: privKey,
}
}

// Dial creates a new grpc client connection to the given target using the atls validator.
func (d *Dialer) Dial(_ context.Context, target string) (*grpc.ClientConn, error) {
var validators []atls.Validator
if d.validator != nil {
validators = append(validators, d.validator)
}
credentials := atlscredentials.NewWithKey(d.issuer, validators, d.privKey)
credentials := atlscredentials.NewWithKey(d.issuer, d.validators, d.privKey)

return grpc.NewClient(target,
d.grpcWithDialer(),
Expand Down
18 changes: 2 additions & 16 deletions internal/manifest/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,13 @@ import (
// Default returns a default manifest with reference values for the given platform.
func Default(platform platforms.Platform) (*Manifest, error) {
embeddedRefValues := GetEmbeddedReferenceValues()

refValues, err := embeddedRefValues.ForPlatform(platform)
if err != nil {
return nil, fmt.Errorf("get reference values for platform %s: %w", platform, err)
}

mnfst := Manifest{}
switch platform {
case platforms.AKSCloudHypervisorSNP:
return &Manifest{
ReferenceValues: ReferenceValues{
AKS: refValues.AKS,
},
}, nil
case platforms.RKE2QEMUTDX, platforms.K3sQEMUTDX:
return &Manifest{
ReferenceValues: ReferenceValues{
BareMetalTDX: refValues.BareMetalTDX,
},
}, nil
}
return &mnfst, nil
return &Manifest{ReferenceValues: *refValues}, nil
}

// GetEmbeddedReferenceValues returns the reference values embedded in the binary.
Expand Down
Loading

0 comments on commit 590bf28

Please sign in to comment.