Skip to content

Commit

Permalink
recoveryapi: cleanup and remove
Browse files Browse the repository at this point in the history
Signed-off-by: Paul Meyer <[email protected]>
  • Loading branch information
katexochen committed Jun 26, 2024
1 parent 504564f commit bc6626f
Show file tree
Hide file tree
Showing 13 changed files with 345 additions and 555 deletions.
2 changes: 0 additions & 2 deletions coordinator/internal/authority/authority.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import (
"github.com/edgelesssys/contrast/internal/attestation/snp"
"github.com/edgelesssys/contrast/internal/ca"
"github.com/edgelesssys/contrast/internal/manifest"
"github.com/edgelesssys/contrast/internal/recoveryapi"
"github.com/edgelesssys/contrast/internal/userapi"
"github.com/google/go-sev-guest/proto/sevsnp"
"github.com/google/go-sev-guest/validate"
Expand Down Expand Up @@ -54,7 +53,6 @@ type Authority struct {
metrics metrics

userapi.UnimplementedUserAPIServer
recoveryapi.UnimplementedRecoveryAPIServer
}

type metrics struct {
Expand Down
39 changes: 0 additions & 39 deletions coordinator/internal/authority/recoveryapi.go

This file was deleted.

143 changes: 0 additions & 143 deletions coordinator/internal/authority/recoveryapi_test.go

This file was deleted.

26 changes: 26 additions & 0 deletions coordinator/internal/authority/userapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,25 @@ func (a *Authority) GetManifests(_ context.Context, _ *userapi.GetManifestsReque
return resp, nil
}

// Recover recovers the Coordinator from a seed and salt.
func (a *Authority) Recover(_ context.Context, req *userapi.RecoverRequest) (*userapi.RecoverResponse, error) {
a.logger.Info("Recover called")

if err := a.initSeedEngine(req.Seed, req.Salt); errors.Is(err, ErrAlreadyRecovered) {
return nil, status.Error(codes.FailedPrecondition, err.Error())
} else if err != nil {
// Pretty sure this failed because the seed was bad.
return nil, status.Errorf(codes.InvalidArgument, "initializing seed engine: %v", err)
}

if err := a.syncState(); err != nil {
// This recovery attempt did not lead to a good state, let's roll it back.
a.se.Store(nil)
return nil, status.Errorf(codes.InvalidArgument, "recovery failed and was rolled back: %v", err)
}
return &userapi.RecoverResponse{}, nil
}

func (a *Authority) validatePeer(ctx context.Context, latest *manifest.Manifest) error {
if len(latest.WorkloadOwnerKeyDigests) == 0 {
return errors.New("setting manifest is disabled")
Expand Down Expand Up @@ -253,3 +272,10 @@ func getPeerPublicKey(ctx context.Context) ([]byte, error) {
}
return x509.MarshalPKIXPublicKey(tlsInfo.State.PeerCertificates[0].PublicKey)
}

var (
// ErrAlreadyRecovered is returned if seedEngine initialization was requested but a seed is already set.
ErrAlreadyRecovered = errors.New("coordinator is already recovered")
// ErrNeedsRecovery is returned if state exists, but no secrets are available, e.g. after restart.
ErrNeedsRecovery = errors.New("coordinator is in recovery mode")
)
123 changes: 123 additions & 0 deletions coordinator/internal/authority/userapi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/tls"
"crypto/x509"
Expand Down Expand Up @@ -247,6 +248,128 @@ func TestGetManifests(t *testing.T) {
assert.Len(resp.Policies, len(m.Policies))
}

func TestRecovery(t *testing.T) {
var seed [32]byte
var salt [32]byte
testCases := []struct {
name string
seed []byte
salt []byte
wantCode codes.Code
}{
{
name: "empty seed",
salt: salt[:],
wantCode: codes.InvalidArgument,
},
{
name: "empty salt",
seed: seed[:],
wantCode: codes.InvalidArgument,
},
{
name: "short seed",
seed: seed[:16],
salt: salt[:],
wantCode: codes.InvalidArgument,
},
{
name: "short salt",
seed: seed[:],
salt: salt[:16],
wantCode: codes.InvalidArgument,
},
{
name: "normal values",
seed: seed[:],
salt: salt[:],
wantCode: codes.OK,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
require := require.New(t)

a := newCoordinator()
_, err := a.Recover(context.Background(), &userapi.RecoverRequest{
Seed: tc.seed,
Salt: tc.salt,
})

require.Equal(tc.wantCode, status.Code(err))
})
}
}

// TestRecoveryFlow exercises the recovery flow's expected path.
func TestRecoveryFlow(t *testing.T) {
require := require.New(t)

// 1. A Coordinator is created from empty state.

a := newCoordinator()

// 2. A manifest is set and the returned seed is recorded.
seedShareOwnerKey, err := rsa.GenerateKey(rand.Reader, 2048)
require.NoError(err)
seedShareOwnerKeyBytes := manifest.MarshalSeedShareOwnerKey(&seedShareOwnerKey.PublicKey)

mnfst, _, policies := newManifest(t)
mnfst.SeedshareOwnerPubKeys = []manifest.HexString{seedShareOwnerKeyBytes}
manifestBytes, err := json.Marshal(mnfst)
require.NoError(err)

req := &userapi.SetManifestRequest{
Manifest: manifestBytes,
Policies: policies,
}
resp1, err := a.SetManifest(context.Background(), req)
require.NoError(err)
require.NotNil(resp1)
seedSharesDoc := resp1.GetSeedSharesDoc()
require.NotNil(seedSharesDoc)
seedShares := seedSharesDoc.GetSeedShares()
require.Len(seedShares, 1)

seed, err := manifest.DecryptSeedShare(seedShareOwnerKey, seedShares[0])
require.NoError(err)

recoverReq := &userapi.RecoverRequest{
Seed: seed,
Salt: seedSharesDoc.GetSalt(),
}

// Recovery on this Coordinator should fail now that a manifest is set.
_, err = a.Recover(context.Background(), recoverReq)
require.ErrorContains(err, ErrAlreadyRecovered.Error())

// 3. A new Coordinator is created with the existing history.
// GetManifests and SetManifest are expected to fail.

a = New(a.hist, prometheus.NewRegistry(), slog.Default())
_, err = a.SetManifest(context.Background(), req)
require.ErrorContains(err, ErrNeedsRecovery.Error())

_, err = a.GetManifests(context.Background(), &userapi.GetManifestsRequest{})
require.ErrorContains(err, ErrNeedsRecovery.Error())

// 4. Recovery is called.
_, err = a.Recover(context.Background(), recoverReq)
require.NoError(err)

// 5. Coordinator should be operational and know about the latest manifest.
resp, err := a.GetManifests(context.Background(), &userapi.GetManifestsRequest{})
require.NoError(err)
require.NotNil(resp)
require.Len(resp.Manifests, 1)
require.Equal([][]byte{manifestBytes}, resp.Manifests)

// Recover on a recovered authority should fail.
_, err = a.Recover(context.Background(), recoverReq)
require.Error(err)
}

// TestUserAPIConcurrent tests potential synchronization problems between the different
// gRPCs of the server.
func TestUserAPIConcurrent(t *testing.T) {
Expand Down
2 changes: 0 additions & 2 deletions coordinator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import (
"github.com/edgelesssys/contrast/internal/grpc/atlscredentials"
"github.com/edgelesssys/contrast/internal/logger"
"github.com/edgelesssys/contrast/internal/meshapi"
"github.com/edgelesssys/contrast/internal/recoveryapi"
"github.com/edgelesssys/contrast/internal/userapi"
grpcprometheus "github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus"
"github.com/prometheus/client_golang/prometheus"
Expand Down Expand Up @@ -69,7 +68,6 @@ func run() (retErr error) {
grpcServer := newGRPCServer(serverMetrics, logger)

userapi.RegisterUserAPIServer(grpcServer, meshAuth)
recoveryapi.RegisterRecoveryAPIServer(grpcServer, meshAuth)
serverMetrics.InitializeMetrics(grpcServer)

eg := errgroup.Group{}
Expand Down
9 changes: 0 additions & 9 deletions internal/recoveryapi/recoveryapi.go

This file was deleted.

Loading

0 comments on commit bc6626f

Please sign in to comment.