From d114d57f444610c9408664bac42280297b252d36 Mon Sep 17 00:00:00 2001 From: Bob Callaway Date: Wed, 30 Oct 2024 15:07:41 -0400 Subject: [PATCH] fix consumer and add test Signed-off-by: Bob Callaway --- go.mod | 1 - pkg/client/rekor_client.go | 2 +- .../restapi/configure_rekor_server.go | 5 +- pkg/tle/e2e_test.go | 20 +++++++ pkg/tle/tle.go | 57 +++++++++++++++++-- 5 files changed, 74 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index 9c04a71e9..2207b1574 100644 --- a/go.mod +++ b/go.mod @@ -58,7 +58,6 @@ require ( github.com/go-redis/redismock/v9 v9.2.0 github.com/go-sql-driver/mysql v1.8.1 github.com/golang/mock v1.6.0 - github.com/golang/protobuf v1.5.4 github.com/hashicorp/go-cleanhttp v0.5.2 github.com/hashicorp/go-retryablehttp v0.7.7 github.com/jmoiron/sqlx v1.4.0 diff --git a/pkg/client/rekor_client.go b/pkg/client/rekor_client.go index 0ed6cf95b..fd7ca4813 100644 --- a/pkg/client/rekor_client.go +++ b/pkg/client/rekor_client.go @@ -65,7 +65,7 @@ func GetRekorClient(rekorServerURL string, opts ...Option) (*client.Rekor, error rt := httptransport.NewWithClient(url.Host, url.Path, []string{url.Scheme}, httpClient) rt.Consumers["application/json"] = runtime.JSONConsumer() rt.Consumers["application/x-pem-file"] = runtime.TextConsumer() - rt.Consumers[tle.TLEMediaType] = tle.TLEConsumer{} + rt.Consumers[tle.TLEMediaType] = &tle.TLEConsumer{} rt.Producers["application/json"] = runtime.JSONProducer() rt.DefaultMediaType = "application/json" diff --git a/pkg/generated/restapi/configure_rekor_server.go b/pkg/generated/restapi/configure_rekor_server.go index f4bc80bf5..833d266cb 100644 --- a/pkg/generated/restapi/configure_rekor_server.go +++ b/pkg/generated/restapi/configure_rekor_server.go @@ -161,9 +161,8 @@ func configureAPI(api *operations.RekorServerAPI) http.Handler { api.ServerShutdown = func() { pkgapi.StopAPI() } - // this causes the order of producers in openapi.yaml to be enforced - api.SetDefaultProduces("") - api.RegisterProducer("*/*", runtime.JSONProducer()) + // the trailing space is intentional to cause checking to fail inside go-openapi but ordering to be enforced from openapi.yaml + api.SetDefaultProduces("application/json ") return setupGlobalMiddleware(api.Serve(setupMiddlewares)) } diff --git a/pkg/tle/e2e_test.go b/pkg/tle/e2e_test.go index 983e63abb..6a56bbed0 100644 --- a/pkg/tle/e2e_test.go +++ b/pkg/tle/e2e_test.go @@ -112,6 +112,26 @@ func TestAcceptTLE(t *testing.T) { if err := parseResponseAsTLEArray(t, resp3); err != nil { t.Fatal(err) } + + // test getting an entry 1000 times using a vague accept header, and ensure that we always return application/json to those request + for j := range 1000 { + req4, err := http.NewRequest("GET", fmt.Sprintf("%s/api/v1/log/entries/%s", util.RekorServer(), uuid), nil) + if err != nil { + t.Fatal(err) + } + // set Accept on every-other call to help get coverage on different scenarios + if j%2 == 0 { + req4.Header.Add("Accept", "*/*") //explicitly accept anything + } + respIter, err := client.Do(req4) + if err != nil { + t.Fatal(err) + } + defer respIter.Body.Close() + if ctHeader := respIter.Header.Get("Content-Type"); ctHeader != "application/json" { + t.Fatalf("unexpected response 'Content-Type' header received: expected 'application/json', got '%s'", ctHeader) + } + } } func parseResponseAsTLE(t *testing.T, resp *http.Response) error { diff --git a/pkg/tle/tle.go b/pkg/tle/tle.go index 596d3a7ff..abaea20bc 100644 --- a/pkg/tle/tle.go +++ b/pkg/tle/tle.go @@ -24,10 +24,12 @@ import ( "io" "github.com/go-openapi/runtime" + "github.com/go-openapi/swag" rekor_pb_common "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1" rekor_pb "github.com/sigstore/protobuf-specs/gen/pb-go/rekor/v1" "github.com/sigstore/rekor/pkg/generated/models" "github.com/sigstore/rekor/pkg/types" + "github.com/transparency-dev/merkle/rfc6962" "google.golang.org/protobuf/encoding/protojson" ) @@ -110,6 +112,36 @@ func MarshalTLEToJSON(tle *rekor_pb.TransparencyLogEntry) ([]byte, error) { return protojson.Marshal(tle) } +func GenerateLogEntry(tle *rekor_pb.TransparencyLogEntry) *models.LogEntry { + if tle == nil { + return nil + } + + entryUUID := hex.EncodeToString(rfc6962.DefaultHasher.HashLeaf(tle.CanonicalizedBody)) + inclusionProofHashes := []string{} + for _, hash := range tle.InclusionProof.Hashes { + inclusionProofHashes = append(inclusionProofHashes, hex.EncodeToString(hash)) + } + return &models.LogEntry{ + entryUUID: models.LogEntryAnon{ + Body: tle.CanonicalizedBody, + IntegratedTime: swag.Int64(tle.IntegratedTime), + LogID: swag.String(tle.LogId.String()), + LogIndex: swag.Int64(tle.LogIndex), + Verification: &models.LogEntryAnonVerification{ + InclusionProof: &models.InclusionProof{ + Checkpoint: swag.String(tle.InclusionProof.Checkpoint.String()), + Hashes: inclusionProofHashes, + LogIndex: swag.Int64(tle.LogIndex), + RootHash: swag.String(hex.EncodeToString(tle.InclusionProof.RootHash)), + TreeSize: swag.Int64(tle.InclusionProof.TreeSize), + }, + SignedEntryTimestamp: tle.InclusionPromise.SignedEntryTimestamp, + }, + }, + } +} + type TLEProducer struct{} func (t TLEProducer) Produce(w io.Writer, input interface{}) error { @@ -178,23 +210,36 @@ func (t TLEConsumer) Consume(r io.Reader, output interface{}) error { if err := json.Unmarshal(tleBytes, &jsonArray); err != nil { return fmt.Errorf("expected array: %w", err) } - var result []*rekor_pb.TransparencyLogEntry for _, element := range jsonArray { msg := &rekor_pb.TransparencyLogEntry{} if err := protojson.Unmarshal(element, msg); err != nil { return fmt.Errorf("parsing element: %w", err) } - result = append(result, msg) + if result, ok := output.([]models.LogEntry); ok { + logEntry := GenerateLogEntry(msg) + result = append(result, *logEntry) + } else if result, ok := output.([]*rekor_pb.TransparencyLogEntry); ok { + result = append(result, msg) + } else { + return errors.New("unsupported conversion") + } } - output = &result return nil case json.Delim('{'): // this is a JSON object, let's check output type to ensure its rekor_pb.TransparencyLogEntry - msg := output.(*rekor_pb.TransparencyLogEntry) - if err := protojson.Unmarshal(tleBytes, msg); err != nil { + tle := &rekor_pb.TransparencyLogEntry{} + if err := protojson.Unmarshal(tleBytes, tle); err != nil { return fmt.Errorf("parsing element: %w", err) } - return nil + if _, ok := output.(*rekor_pb.TransparencyLogEntry); ok { + output = tle + return nil + } else if _, ok := output.(*models.LogEntry); ok { + output = GenerateLogEntry(tle) + return nil + } else { + return errors.New("unsupported conversion") + } } return errors.New("unexpected value") }