Skip to content

Commit

Permalink
Update bastion feeder & related
Browse files Browse the repository at this point in the history
  • Loading branch information
AlCutter committed Jan 28, 2025
1 parent 6792259 commit 3fb247c
Show file tree
Hide file tree
Showing 6 changed files with 50 additions and 64 deletions.
2 changes: 1 addition & 1 deletion cmd/feedbastion/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ func (b *bastionClient) GetLatestCheckpoint(ctx context.Context, logID string) (
// Update attempts to clock the witness forward for the given logID.
// The latest signed checkpoint will be returned if this succeeds, or if the error is
// http.ErrCheckpointTooOld. In all other cases no checkpoint should be expected.
func (b *bastionClient) Update(ctx context.Context, logID string, newCP []byte, proof [][]byte) ([]byte, error) {
func (b *bastionClient) Update(ctx context.Context, logID string, oldSize uint64, newCP []byte, proof [][]byte) ([]byte, error) {
// The request body MUST be a sequence of
// - a previous size line,
// - zero or more consistency proof lines,
Expand Down
45 changes: 18 additions & 27 deletions internal/feeder/bastion/bastion_feeder.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,11 @@ import (
"github.com/transparency-dev/formats/log"
"github.com/transparency-dev/witness/internal/config"
"github.com/transparency-dev/witness/internal/feeder"
"github.com/transparency-dev/witness/internal/witness"
"github.com/transparency-dev/witness/monitoring"
"golang.org/x/mod/sumdb/note"
"golang.org/x/net/http2"
"golang.org/x/time/rate"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"k8s.io/klog/v2"
)

Expand Down Expand Up @@ -167,8 +166,11 @@ func (a *addHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
counterBastionIncomingResponse.Inc(bastionID, logCfg.Origin, strconv.Itoa(sc))
}

// handleUpdate submits the provided checkpoint to the witness and interprets any errors which may result.
//
// Returns an appropriate HTTP status code, response body, and Content Type representing the outcome.
func (a *addHandler) handleUpdate(ctx context.Context, logID string, origin string, oldSize uint64, newCP []byte, proof [][]byte) (int, []byte, string, error) {
trusted, updateErr := a.w.Update(ctx, logID, newCP, proof)
trusted, updateErr := a.w.Update(ctx, logID, oldSize, newCP, proof)
// Whatever happened, we usually get the latest trusted CP from the witness (whether it's the old one or the one we've just updated to).
// If we get nothing at all, then something's gone quite wrong.
if trusted == nil {
Expand All @@ -182,32 +184,21 @@ func (a *addHandler) handleUpdate(ctx context.Context, logID string, origin stri

// Finally, handle any "soft" error from the update:
if updateErr != nil {
switch sc := status.Code(updateErr); sc {
case codes.Unauthenticated:
// The proof is invalid somehow, this could be because the proof bytes are wrong, but it could also be that the log's idea of what the
// witness' current trusted checkpoint is wrong. So figure out which, and respond accordingly.

if trustedCP.Size != oldSize {
// The witness MUST check that the old size matches the size of the latest checkpoint it cosigned for the checkpoint's origin (or zero if it never
// cosigned a checkpoint for that origin). If it doesn't match, the witness MUST respond with a "409 Conflict" HTTP status code.
// The response body MUST consist of the tree size of the latest cosigned checkpoint in decimal, followed by a newline (U+000A).
// The response MUST have a Content-Type of text/x.tlog.size.
body := []byte(fmt.Sprintf("%d\n", trustedCP.Size))
return http.StatusConflict, body, "text/x.tlog.size", nil
}

// Invalid proof
// The consistency proof lines MUST encode a Merkle Consistency Proof from the old size to the checkpoint size according to RFC 6962, Section 2.1.2.
// The proof MUST be empty if the old size is zero.
// If the Merkle Consistency Proof doesn't verify, the witness MUST respond with a "422 Unprocessable Entity" HTTP status code.
switch updateErr {
case witness.ErrCheckpointStale:
return http.StatusConflict, []byte(fmt.Sprintf("%d\n", trustedCP.Size)), "text/x.tlog.size", nil
case witness.ErrUnknownLog:
return http.StatusNotFound, nil, "", nil
case witness.ErrNoValidSignature:
return http.StatusForbidden, nil, "", nil
case witness.ErrOldSizeInvalid:
return http.StatusBadRequest, nil, "", nil
case witness.ErrInvalidProof:
return http.StatusUnprocessableEntity, nil, "", nil
case codes.FailedPrecondition:
// If the old size matches the checkpoint size, the witness MUST check that the root hashes are also identical.
// If they don't match, the witness MUST respond with a "409 Conflict" HTTP status code.
case witness.ErrRootMismatch:
return http.StatusConflict, nil, "", nil
case codes.AlreadyExists:
body := []byte(fmt.Sprintf("%d\n", trustedCP.Size))
return http.StatusConflict, body, "text/x.tlog.size", nil
default:
return http.StatusInternalServerError, nil, "", updateErr
}
}

Expand Down
55 changes: 25 additions & 30 deletions internal/feeder/bastion/bastion_feeder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/transparency-dev/formats/note"
"github.com/transparency-dev/witness/internal/config"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/transparency-dev/witness/internal/witness"
)

const (
Expand All @@ -35,7 +34,6 @@ const (
testCPRoot = "7azctENRYLlBCBQ5OX2qxxIKCTOeCda1KfTwjdt0wdA="
testCPSig = "— transparency.dev-aw-ftlog-ci-2 93xidocoWXVph2jEuzW2oovU+IjU71+FeVGKtKXQknSla2HCvr6RYHRSdJfxpo4kj5geqxkjrDXcbpiSo7lK96X4Dgc=\n"

//testCP = "transparency.dev/armored-witness/firmware_transparency/ci/2\n56\n7azctENRYLlBCBQ5OX2qxxIKCTOeCda1KfTwjdt0wdA=\n\n— transparency.dev-aw-ftlog-ci-2 93xidocoWXVph2jEuzW2oovU+IjU71+FeVGKtKXQknSla2HCvr6RYHRSdJfxpo4kj5geqxkjrDXcbpiSo7lK96X4Dgc=\n"
testCPVerifier = "transparency.dev-aw-ftlog-ci-2+f77c6276+AZXqiaARpwF4MoNOxx46kuiIRjrML0PDTm+c7BLaAMt6"
)

Expand Down Expand Up @@ -100,10 +98,7 @@ func TestHandler(t *testing.T) {
logID: config.Log{Origin: testCPOrigin},
}
for _, test := range []struct {
// params
name string
logID string
oldSize uint64
name string
// fake witness control
witnessResp []byte
witnessErr error
Expand All @@ -113,41 +108,41 @@ func TestHandler(t *testing.T) {
wantContentType string
}{
{
name: "works - accepted by witness",
logID: "logID",
name: "Accepted by witness",
witnessResp: []byte(testCP),
wantStatus: 200,
wantBody: testCPSig,
}, {
name: "new CP smaller than existing",
logID: "logID",
name: "ErrCheckpointStale",
witnessResp: []byte(testCP),
witnessErr: status.Errorf(codes.AlreadyExists, "test error"),
witnessErr: witness.ErrCheckpointStale,
wantStatus: http.StatusConflict,
wantContentType: "text/x.tlog.size",
wantBody: fmt.Sprintf("%d\n", testCPSize),
}, {
name: "invalid proof",
logID: "logID",
oldSize: testCPSize,
name: "ErrNoValidSignature",
witnessResp: []byte(testCP),
witnessErr: status.Errorf(codes.Unauthenticated, "test error"),
witnessErr: witness.ErrNoValidSignature,
wantStatus: http.StatusForbidden,
}, {
name: "ErrUnknownLog",
witnessResp: []byte(testCP),
witnessErr: witness.ErrUnknownLog,
wantStatus: http.StatusNotFound,
}, {
name: "ErrInvalidProof",
witnessResp: []byte(testCP),
witnessErr: witness.ErrInvalidProof,
wantStatus: http.StatusUnprocessableEntity,
}, {
name: "incorrect oldCP size",
logID: "logID",
oldSize: testCPSize - 10,
witnessResp: []byte(testCP),
witnessErr: status.Errorf(codes.Unauthenticated, "test error"),
wantStatus: http.StatusConflict,
wantContentType: "text/x.tlog.size",
wantBody: fmt.Sprintf("%d\n", testCPSize),
name: "ErrOldSizeInvalid",
witnessResp: []byte(testCP),
witnessErr: witness.ErrOldSizeInvalid,
wantStatus: http.StatusBadRequest,
}, {
name: "same size, different roots",
logID: "logID",
oldSize: testCPSize,
name: "ErrRootMismatch",
witnessResp: []byte(testCP),
witnessErr: status.Errorf(codes.FailedPrecondition, "test error"),
witnessErr: witness.ErrRootMismatch,
wantStatus: http.StatusConflict,
},
} {
Expand All @@ -160,7 +155,7 @@ func TestHandler(t *testing.T) {
if err != nil {
t.Fatalf("NewRequest: %v", err)
}
sc, body, ct, err := a.handleUpdate(context.Background(), test.logID, testCPOrigin, test.oldSize, []byte(testCP), [][]byte{})
sc, body, ct, err := a.handleUpdate(context.Background(), "logID", testCPOrigin, 0, []byte(testCP), [][]byte{})
if err != nil {
t.Fatalf("handleUpdate: %v", err)
}
Expand Down Expand Up @@ -188,7 +183,7 @@ func (tw *testWitness) GetLatestCheckpoint(ctx context.Context, logID string) ([
return tw.latestCP, tw.latestCPErr
}

func (tw *testWitness) Update(ctx context.Context, logID string, newCP []byte, proof [][]byte) ([]byte, error) {
func (tw *testWitness) Update(ctx context.Context, logID string, oldSize uint64, newCP []byte, proof [][]byte) ([]byte, error) {
return tw.updateResponse, tw.updateErr
}

Expand Down
6 changes: 3 additions & 3 deletions internal/feeder/feeder.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ type Witness interface {
// Update attempts to clock the witness forward for the given logID.
// The latest signed checkpoint will be returned if this succeeds, or if the error is
// http.ErrCheckpointTooOld. In all other cases no checkpoint should be expected.
Update(ctx context.Context, logID string, newCP []byte, proof [][]byte) ([]byte, error)
Update(ctx context.Context, logID string, oldSize uint64, newCP []byte, proof [][]byte) ([]byte, error)
}

// FeedOpts holds parameters when calling the Feed function.
Expand Down Expand Up @@ -150,7 +150,7 @@ func submitToWitness(ctx context.Context, cpRaw []byte, cpSubmit log.Checkpoint,
}
if latestCP.Size == cpSubmit.Size && bytes.Equal(latestCP.Hash, cpSubmit.Hash) {
klog.V(1).Infof("%q unchanged - @%d: %x", logName, latestCP.Size, latestCP.Hash)
if returnCp, err = opts.Witness.Update(ctx, opts.LogID, cpRaw, [][]byte{}); err != nil {
if returnCp, err = opts.Witness.Update(ctx, opts.LogID, latestCP.Size, cpRaw, [][]byte{}); err != nil {
e := fmt.Errorf("failed to submit fresh checkpoint to witness: %v", err)
klog.Warning(e.Error())
return e
Expand All @@ -171,7 +171,7 @@ func submitToWitness(ctx context.Context, cpRaw []byte, cpSubmit log.Checkpoint,
}
klog.V(2).Infof("%q %d -> %d proof: %x", logName, latestCP.Size, cpSubmit.Size, conP)

if returnCp, err = opts.Witness.Update(ctx, opts.LogID, cpRaw, conP); err != nil {
if returnCp, err = opts.Witness.Update(ctx, opts.LogID, latestCP.Size, cpRaw, conP); err != nil {
e := fmt.Errorf("failed to submit checkpoint to witness: %v", err)
klog.Warning(e.Error())
return e
Expand Down
2 changes: 1 addition & 1 deletion internal/feeder/feeder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ func (fw *fakeWitness) GetLatestCheckpoint(_ context.Context, logID string) ([]b
return fw.latestCP, nil
}

func (fw *fakeWitness) Update(_ context.Context, logID string, newCP []byte, proof [][]byte) ([]byte, error) {
func (fw *fakeWitness) Update(_ context.Context, logID string, oldSize uint64, newCP []byte, proof [][]byte) ([]byte, error) {
if fw.rejectUpdate {
return nil, errors.New("computer says 'no'")
}
Expand Down
4 changes: 2 additions & 2 deletions omniwitness/omniwitness.go
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,6 @@ func (w witnessAdapter) GetLatestCheckpoint(ctx context.Context, logID string) (
return cp, err
}

func (w witnessAdapter) Update(ctx context.Context, logID string, newCP []byte, proof [][]byte) ([]byte, error) {
return w.w.Update(ctx, logID, newCP, proof)
func (w witnessAdapter) Update(ctx context.Context, logID string, oldSize uint64, newCP []byte, proof [][]byte) ([]byte, error) {
return w.w.Update(ctx, logID, oldSize, newCP, proof)
}

0 comments on commit 3fb247c

Please sign in to comment.