Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

recovery: add server and recover authority #570

Merged
merged 1 commit into from
Jun 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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