Skip to content

Commit

Permalink
recovery: add server and recover authority
Browse files Browse the repository at this point in the history
Signed-off-by: Paul Meyer <[email protected]>
  • Loading branch information
katexochen committed Jun 14, 2024
1 parent 4bcf1a6 commit 131b5d8
Show file tree
Hide file tree
Showing 7 changed files with 476 additions and 1 deletion.
18 changes: 18 additions & 0 deletions coordinator/internal/authority/authority.go
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,24 @@ func (m *Authority) LatestManifest() (*manifest.Manifest, error) {
return c.manifest, nil
}

// Recoverable returns whether the Authority can be recovered from a persisted state.
func (m *Authority) Recoverable() (bool, error) {
return m.hist.HasLatest()
}

// Recover recovers the seed engine from a seed and salt.
func (m *Authority) Recover(seed, salt []byte) error {
seedEngine, err := seedengine.New(seed, salt)
if err != nil {
return fmt.Errorf("creating seed engine: %w", err)
}
if !m.se.CompareAndSwap(nil, seedEngine) {
return errors.New("already recovered")
}
m.hist.ConfigureSigningKey(m.se.Load().TransactionSigningKey())
return nil
}

// createSeedEngine populates m.se.
//
// It is fine to call this function concurrently. After it returns, m.se is guaranteed to be
Expand Down
23 changes: 22 additions & 1 deletion coordinator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/edgelesssys/contrast/coordinator/internal/authority"
"github.com/edgelesssys/contrast/internal/logger"
"github.com/edgelesssys/contrast/internal/meshapi"
"github.com/edgelesssys/contrast/internal/recoveryapi"
"github.com/edgelesssys/contrast/internal/userapi"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
Expand Down Expand Up @@ -49,19 +50,39 @@ func run() (retErr error) {
}

metricsPort := os.Getenv(metricsPortEnvVar)

promRegistry := prometheus.NewRegistry()

hist, err := history.New()
if err != nil {
return fmt.Errorf("creating history: %w", err)
}

meshAuth := authority.New(hist, promRegistry, logger)
recoveryAPI := newRecoveryAPIServer(meshAuth, promRegistry, logger)
userAPI := newUserAPIServer(meshAuth, promRegistry, logger)
meshAPI := newMeshAPIServer(meshAuth, meshAuth, promRegistry, logger)

eg := errgroup.Group{}

recoverable, err := meshAuth.Recoverable()
if err != nil {
return fmt.Errorf("checking recoverability: %w", err)
}
if recoverable {
logger.Warn("Coordinator is in recovery mode")

eg.Go(func() error {
logger.Info("Coordinator recovery API listening")
if err := recoveryAPI.Serve(net.JoinHostPort("0.0.0.0", recoveryapi.Port)); err != nil {
return fmt.Errorf("serving recovery API: %w", err)
}
return nil
})

recoveryAPI.WaitRecoveryDone()
logger.Info("Coordinator recovery done")
}

eg.Go(func() error {
if metricsPort == "" {
return nil
Expand Down
88 changes: 88 additions & 0 deletions coordinator/recoveryapi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright 2024 Edgeless Systems GmbH
// SPDX-License-Identifier: AGPL-3.0-only

package main

import (
"context"
"fmt"
"log/slog"
"net"
"time"

"github.com/edgelesssys/contrast/internal/attestation/snp"
"github.com/edgelesssys/contrast/internal/grpc/atlscredentials"
"github.com/edgelesssys/contrast/internal/logger"
"github.com/edgelesssys/contrast/internal/recoveryapi"
grpcprometheus "github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus"
"github.com/prometheus/client_golang/prometheus"
"google.golang.org/grpc"
"google.golang.org/grpc/keepalive"
)

type recoveryAPIServer struct {
grpc *grpc.Server
logger *slog.Logger
recoverable recoverable
recoveryDoneC chan struct{}

recoveryapi.UnimplementedRecoveryAPIServer
}

func newRecoveryAPIServer(recoveryTarget recoverable, reg *prometheus.Registry, log *slog.Logger) *recoveryAPIServer {
issuer := snp.NewIssuer(logger.NewNamed(log, "snp-issuer"))
credentials := atlscredentials.New(issuer, nil)

grpcUserAPIMetrics := grpcprometheus.NewServerMetrics(
grpcprometheus.WithServerCounterOptions(
grpcprometheus.WithSubsystem("contrast_recoveryapi"),
),
grpcprometheus.WithServerHandlingTimeHistogram(
grpcprometheus.WithHistogramSubsystem("contrast_recoveryapi"),
grpcprometheus.WithHistogramBuckets([]float64{0.0001, 0.0005, 0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1, 2.5, 5}),
),
)

grpcServer := grpc.NewServer(
grpc.Creds(credentials),
grpc.KeepaliveParams(keepalive.ServerParameters{Time: 15 * time.Second}),
grpc.ChainStreamInterceptor(
grpcUserAPIMetrics.StreamServerInterceptor(),
),
grpc.ChainUnaryInterceptor(
grpcUserAPIMetrics.UnaryServerInterceptor(),
),
)
s := &recoveryAPIServer{
grpc: grpcServer,
logger: log.WithGroup("recoveryapi"),
recoverable: recoveryTarget,
recoveryDoneC: make(chan struct{}),
}
recoveryapi.RegisterRecoveryAPIServer(s.grpc, s)

grpcUserAPIMetrics.InitializeMetrics(grpcServer)
reg.MustRegister(grpcUserAPIMetrics)

return s
}

func (s *recoveryAPIServer) Serve(endpoint string) error {
lis, err := net.Listen("tcp", endpoint)
if err != nil {
return fmt.Errorf("listening on %s: %w", endpoint, err)
}
return s.grpc.Serve(lis)
}

func (s *recoveryAPIServer) WaitRecoveryDone() {
<-s.recoveryDoneC
}

func (s *recoveryAPIServer) Recover(_ context.Context, req *recoveryapi.RecoverRequest) (*recoveryapi.RecoverResponse, error) {
return &recoveryapi.RecoverResponse{}, s.recoverable.Recover(req.Seed, req.Salt)
}

type recoverable interface {
Recover(seed, salt []byte) error
}
9 changes: 9 additions & 0 deletions internal/recoveryapi/recoveryapi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright 2024 Edgeless Systems GmbH
// SPDX-License-Identifier: AGPL-3.0-only

package recoveryapi

//go:generate protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative recoveryapi.proto

// Port is the port of the coordinator API.
const Port = "1314"
214 changes: 214 additions & 0 deletions internal/recoveryapi/recoveryapi.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 131b5d8

Please sign in to comment.