From 9075c4748df876a0a78c0818b347da796b4e4d77 Mon Sep 17 00:00:00 2001 From: Bob Callaway Date: Tue, 27 Sep 2022 13:32:46 -0400 Subject: [PATCH 1/2] add ability to enable/disable specific rekor API endpoints Signed-off-by: Bob Callaway --- cmd/rekor-server/app/root.go | 14 +++ pkg/api/api.go | 3 +- pkg/api/entries.go | 40 ++++++- pkg/api/public_key.go | 12 ++ pkg/api/tlog.go | 20 ++++ .../restapi/configure_rekor_server.go | 110 ++++++++++-------- 6 files changed, 149 insertions(+), 50 deletions(-) diff --git a/cmd/rekor-server/app/root.go b/cmd/rekor-server/app/root.go index ac2d7e385..a09b31b29 100644 --- a/cmd/rekor-server/app/root.go +++ b/cmd/rekor-server/app/root.go @@ -32,6 +32,17 @@ var ( cfgFile string logType string enablePprof bool + // these map to the operationId as defined in openapi.yaml file + operationIds = []string{ + "searchIndex", + "getLogInfo", + "getPublicKey", + "getLogProof", + "createLogEntry", + "getLogEntryByIndex", + "getLogEntryByUUID", + "searchLogQuery", + } ) // rootCmd represents the base command when called without any subcommands @@ -84,6 +95,7 @@ Memory and file-based signers should only be used for testing.`) rootCmd.PersistentFlags().Uint16("port", 3000, "Port to bind to") rootCmd.PersistentFlags().Bool("enable_retrieve_api", true, "enables Redis-based index API endpoint") + rootCmd.PersistentFlags().MarkDeprecated("enable_retrieve_api", "this flag is deprecated in favor of enabled_api_endpoints (searchIndex)") rootCmd.PersistentFlags().String("redis_server.address", "127.0.0.1", "Redis server address") rootCmd.PersistentFlags().Uint16("redis_server.port", 6379, "Redis server port") @@ -91,6 +103,8 @@ Memory and file-based signers should only be used for testing.`) rootCmd.PersistentFlags().String("attestation_storage_bucket", "", "url for attestation storage bucket") rootCmd.PersistentFlags().Int("max_attestation_size", 100*1024, "max size for attestation storage, in bytes") + rootCmd.PersistentFlags().StringSlice("enabled_api_endpoints", operationIds, "list of API endpoints to enable using operationId from openapi.yaml") + if err := viper.BindPFlags(rootCmd.PersistentFlags()); err != nil { log.Logger.Fatal(err) } diff --git a/pkg/api/api.go b/pkg/api/api.go index 1aa031bdd..84ca4ac06 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -26,6 +26,7 @@ import ( "github.com/google/trillian" radix "github.com/mediocregopher/radix/v4" "github.com/spf13/viper" + "golang.org/x/exp/slices" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" @@ -133,7 +134,7 @@ func ConfigureAPI(treeID uint) { if err != nil { log.Logger.Panic(err) } - if viper.GetBool("enable_retrieve_api") { + if viper.GetBool("enable_retrieve_api") || slices.Contains(viper.GetStringSlice("enabled_api_endpoints"), "searchIndex") { redisClient, err = cfg.New(context.Background(), "tcp", fmt.Sprintf("%v:%v", viper.GetString("redis_server.address"), viper.GetUint64("redis_server.port"))) if err != nil { log.Logger.Panic("failure connecting to redis instance: ", err) diff --git a/pkg/api/entries.go b/pkg/api/entries.go index 64251a16a..e522ba175 100644 --- a/pkg/api/entries.go +++ b/pkg/api/entries.go @@ -232,7 +232,7 @@ func createLogEntry(params entries.CreateLogEntryParams) (models.LogEntry, middl IntegratedTime: swag.Int64(queuedLeaf.IntegrateTimestamp.AsTime().Unix()), } - if viper.GetBool("enable_retrieve_api") { + if redisClient != nil { go func() { keys, err := entry.IndexKeys() if err != nil { @@ -576,3 +576,41 @@ func retrieveUUIDFromTree(ctx context.Context, uuid string, tid int64) (models.L return models.LogEntry{}, errors.New("unexpected error") } } + +// handlers for APIs that may be disabled in a given instance + +func CreateLogEntryNotImplementedHandler(params entries.CreateLogEntryParams) middleware.Responder { + err := &models.Error{ + Code: http.StatusNotImplemented, + Message: "Create Entry API not enabled in this Rekor instance", + } + + return entries.NewCreateLogEntryDefault(http.StatusNotImplemented).WithPayload(err) +} + +func GetLogEntryByIndexNotImplementedHandler(params entries.GetLogEntryByIndexParams) middleware.Responder { + err := &models.Error{ + Code: http.StatusNotImplemented, + Message: "Get Log Entry by Index API not enabled in this Rekor instance", + } + + return entries.NewGetLogEntryByIndexDefault(http.StatusNotImplemented).WithPayload(err) +} + +func GetLogEntryByUUIDNotImplementedHandler(params entries.GetLogEntryByUUIDParams) middleware.Responder { + err := &models.Error{ + Code: http.StatusNotImplemented, + Message: "Get Log Entry by UUID API not enabled in this Rekor instance", + } + + return entries.NewGetLogEntryByUUIDDefault(http.StatusNotImplemented).WithPayload(err) +} + +func SearchLogQueryNotImplementedHandler(params entries.SearchLogQueryParams) middleware.Responder { + err := &models.Error{ + Code: http.StatusNotImplemented, + Message: "Search Log Query API not enabled in this Rekor instance", + } + + return entries.NewSearchLogQueryDefault(http.StatusNotImplemented).WithPayload(err) +} diff --git a/pkg/api/public_key.go b/pkg/api/public_key.go index 8dee1150e..22e0c0436 100644 --- a/pkg/api/public_key.go +++ b/pkg/api/public_key.go @@ -21,6 +21,7 @@ import ( "github.com/go-openapi/runtime/middleware" "github.com/go-openapi/swag" + "github.com/sigstore/rekor/pkg/generated/models" "github.com/sigstore/rekor/pkg/generated/restapi/operations/pubkey" ) @@ -34,3 +35,14 @@ func GetPublicKeyHandler(params pubkey.GetPublicKeyParams) middleware.Responder } return pubkey.NewGetPublicKeyOK().WithPayload(pk) } + +// handlers for APIs that may be disabled in a given instance + +func GetPublicKeyNotImplementedHandler(params pubkey.GetPublicKeyParams) middleware.Responder { + err := &models.Error{ + Code: http.StatusNotImplemented, + Message: "Get Public Key API not enabled in this Rekor instance", + } + + return pubkey.NewGetPublicKeyDefault(http.StatusNotImplemented).WithPayload(err) +} diff --git a/pkg/api/tlog.go b/pkg/api/tlog.go index 33e7d699e..e6bbdcc68 100644 --- a/pkg/api/tlog.go +++ b/pkg/api/tlog.go @@ -164,3 +164,23 @@ func inactiveShardLogInfo(ctx context.Context, tid int64) (*models.InactiveShard } return &m, nil } + +// handlers for APIs that may be disabled in a given instance + +func GetLogInfoNotImplementedHandler(params tlog.GetLogInfoParams) middleware.Responder { + err := &models.Error{ + Code: http.StatusNotImplemented, + Message: "Get Log Info API not enabled in this Rekor instance", + } + + return tlog.NewGetLogInfoDefault(http.StatusNotImplemented).WithPayload(err) +} + +func GetLogProofNotImplementedHandler(params tlog.GetLogProofParams) middleware.Responder { + err := &models.Error{ + Code: http.StatusNotImplemented, + Message: "Get Log Proof API not enabled in this Rekor instance", + } + + return tlog.NewGetLogProofDefault(http.StatusNotImplemented).WithPayload(err) +} diff --git a/pkg/generated/restapi/configure_rekor_server.go b/pkg/generated/restapi/configure_rekor_server.go index 6df6070d4..c9d9dc6b9 100644 --- a/pkg/generated/restapi/configure_rekor_server.go +++ b/pkg/generated/restapi/configure_rekor_server.go @@ -43,7 +43,7 @@ import ( "github.com/sigstore/rekor/pkg/log" "github.com/sigstore/rekor/pkg/util" - "github.com/urfave/negroni" + "golang.org/x/exp/slices" ) //go:generate swagger generate server --target ../../generated --name RekorServer --spec ../../../openapi.yaml --principal interface{} --exclude-main @@ -84,49 +84,75 @@ func configureAPI(api *operations.RekorServerAPI) http.Handler { api.ApplicationXPemFileProducer = runtime.TextProducer() - api.EntriesCreateLogEntryHandler = entries.CreateLogEntryHandlerFunc(pkgapi.CreateLogEntryHandler) - api.EntriesGetLogEntryByIndexHandler = entries.GetLogEntryByIndexHandlerFunc(pkgapi.GetLogEntryByIndexHandler) - api.EntriesGetLogEntryByUUIDHandler = entries.GetLogEntryByUUIDHandlerFunc(pkgapi.GetLogEntryByUUIDHandler) - api.EntriesSearchLogQueryHandler = entries.SearchLogQueryHandlerFunc(pkgapi.SearchLogQueryHandler) - - api.PubkeyGetPublicKeyHandler = pubkey.GetPublicKeyHandlerFunc(pkgapi.GetPublicKeyHandler) - - api.TlogGetLogInfoHandler = tlog.GetLogInfoHandlerFunc(pkgapi.GetLogInfoHandler) - api.TlogGetLogProofHandler = tlog.GetLogProofHandlerFunc(pkgapi.GetLogProofHandler) + // disable all endpoints to start + api.IndexSearchIndexHandler = index.SearchIndexHandlerFunc(pkgapi.SearchIndexNotImplementedHandler) + api.EntriesCreateLogEntryHandler = entries.CreateLogEntryHandlerFunc(pkgapi.CreateLogEntryNotImplementedHandler) + api.EntriesGetLogEntryByIndexHandler = entries.GetLogEntryByIndexHandlerFunc(pkgapi.GetLogEntryByIndexNotImplementedHandler) + api.EntriesGetLogEntryByUUIDHandler = entries.GetLogEntryByUUIDHandlerFunc(pkgapi.GetLogEntryByUUIDNotImplementedHandler) + api.EntriesSearchLogQueryHandler = entries.SearchLogQueryHandlerFunc(pkgapi.SearchLogQueryNotImplementedHandler) + api.PubkeyGetPublicKeyHandler = pubkey.GetPublicKeyHandlerFunc(pkgapi.GetPublicKeyNotImplementedHandler) + api.TlogGetLogProofHandler = tlog.GetLogProofHandlerFunc(pkgapi.GetLogProofNotImplementedHandler) + + enabled_api_endpoints := viper.GetStringSlice("enabled_api_endpoints") + if !slices.Contains(enabled_api_endpoints, "searchIndex") && viper.GetBool("enable_retrieve_api") { + enabled_api_endpoints = append(enabled_api_endpoints, "searchIndex") + } - if viper.GetBool("enable_retrieve_api") { - api.IndexSearchIndexHandler = index.SearchIndexHandlerFunc(pkgapi.SearchIndexHandler) - } else { - api.IndexSearchIndexHandler = index.SearchIndexHandlerFunc(pkgapi.SearchIndexNotImplementedHandler) + for _, enabled_api := range enabled_api_endpoints { + log.Logger.Infof("Enabling API endpoint: %s", enabled_api) + switch enabled_api { + case "searchIndex": + api.IndexSearchIndexHandler = index.SearchIndexHandlerFunc(pkgapi.SearchIndexHandler) + case "getLogInfo": + api.TlogGetLogInfoHandler = tlog.GetLogInfoHandlerFunc(pkgapi.GetLogInfoHandler) + case "getPublicKey": + api.PubkeyGetPublicKeyHandler = pubkey.GetPublicKeyHandlerFunc(pkgapi.GetPublicKeyHandler) + case "getLogProof": + api.TlogGetLogProofHandler = tlog.GetLogProofHandlerFunc(pkgapi.GetLogProofHandler) + case "createLogEntry": + api.EntriesCreateLogEntryHandler = entries.CreateLogEntryHandlerFunc(pkgapi.CreateLogEntryHandler) + case "getLogEntryByIndex": + api.EntriesGetLogEntryByIndexHandler = entries.GetLogEntryByIndexHandlerFunc(pkgapi.GetLogEntryByIndexHandler) + case "getLogEntryByUUID": + api.EntriesGetLogEntryByUUIDHandler = entries.GetLogEntryByUUIDHandlerFunc(pkgapi.GetLogEntryByUUIDHandler) + case "searchLogQuery": + api.EntriesSearchLogQueryHandler = entries.SearchLogQueryHandlerFunc(pkgapi.SearchLogQueryHandler) + default: + log.Logger.Panicf("Unknown API endpoint requested: %s", enabled_api) + } } + // all handlers need to be set before a call to api.AddMiddlewareFor + for _, enabled_api := range enabled_api_endpoints { + switch enabled_api { + case "searchIndex": + recordMetricsForAPI(api, "POST", "/api/v1/index/retrieve") // add metrics + case "getLogInfo": + api.AddMiddlewareFor("GET", "/api/v1/log", middleware.NoCache) // not cacheable + recordMetricsForAPI(api, "GET", "/api/v1/log") // add metrics + case "getPublicKey": + api.AddMiddlewareFor("GET", "/api/v1/log/publicKey", middleware.NoCache) // not cacheable + recordMetricsForAPI(api, "GET", "/api/v1/log/publicKey") // add metrics + case "getLogProof": + api.AddMiddlewareFor("GET", "/api/v1/log/proof", middleware.NoCache) // not cacheable + recordMetricsForAPI(api, "GET", "/api/v1/log/proof") // add metrics + case "createLogEntry": + recordMetricsForAPI(api, "POST", "/api/v1/log/entries") // add metrics + case "getLogEntryByIndex": + api.AddMiddlewareFor("GET", "/api/v1/log/entries", middleware.NoCache) // not cacheable + recordMetricsForAPI(api, "GET", "/api/v1/log/entries") // add metrics + case "getLogEntryByUUID": + api.AddMiddlewareFor("GET", "/api/v1/log/entries/{entryUUID}", middleware.NoCache) // not cacheable + recordMetricsForAPI(api, "GET", "/api/v1/log/entries/{entryUUID}") // add metrics + case "searchLogQuery": + recordMetricsForAPI(api, "POST", "/api/v1/log/entries/retrieve") // add metrics + } + } api.RegisterFormat("signedCheckpoint", &util.SignedNote{}, util.SignedCheckpointValidator) api.PreServerShutdown = func() {} - api.ServerShutdown = func() {} - // not cacheable - api.AddMiddlewareFor("GET", "/api/v1/log", middleware.NoCache) - api.AddMiddlewareFor("GET", "/api/v1/log/proof", middleware.NoCache) - api.AddMiddlewareFor("GET", "/api/v1/log/entries", middleware.NoCache) - api.AddMiddlewareFor("GET", "/api/v1/log/entries/{entryUUID}", middleware.NoCache) - api.AddMiddlewareFor("GET", "/api/v1/timestamp", middleware.NoCache) - - // cache forever - api.AddMiddlewareFor("GET", "/api/v1/log/publicKey", cacheForever) - api.AddMiddlewareFor("GET", "/api/v1/log/timestamp/certchain", cacheForever) - - // add metrics for explicitly handled endpoints - recordMetricsForAPI(api, "POST", "/api/v1/index/retrieve") - recordMetricsForAPI(api, "GET", "/api/v1/log") - recordMetricsForAPI(api, "GET", "/api/v1/publicKey") - recordMetricsForAPI(api, "GET", "/api/v1/log/proof") - recordMetricsForAPI(api, "GET", "/api/v1/log/entries") - recordMetricsForAPI(api, "POST", "/api/v1/log/entries") - recordMetricsForAPI(api, "GET", "/api/v1/log/entries/{entryUUID}") - recordMetricsForAPI(api, "GET", "/api/v1/log/entries/retrieve") - return setupGlobalMiddleware(api.Serve(setupMiddlewares)) } @@ -242,18 +268,6 @@ func wrapMetrics(handler http.Handler) http.Handler { }) } -func cacheForever(handler http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ww := negroni.NewResponseWriter(w) - ww.Before(func(w negroni.ResponseWriter) { - if w.Status() >= 200 && w.Status() <= 299 { - w.Header().Set("Cache-Control", "s-maxage=31536000, max-age=31536000, immutable") - } - }) - handler.ServeHTTP(ww, r) - }) -} - func logAndServeError(w http.ResponseWriter, r *http.Request, err error) { ctx := r.Context() if apiErr, ok := err.(errors.Error); ok && apiErr.Code() == http.StatusNotFound { From 994a98954c1264d8ab7dd45755464bc6dbe83494 Mon Sep 17 00:00:00 2001 From: Bob Callaway Date: Tue, 27 Sep 2022 14:59:55 -0400 Subject: [PATCH 2/2] fix linter, run go mod tidy Signed-off-by: Bob Callaway --- cmd/rekor-server/app/root.go | 2 +- go.mod | 1 - go.sum | 2 -- .../restapi/configure_rekor_server.go | 18 +++++++++--------- 4 files changed, 10 insertions(+), 13 deletions(-) diff --git a/cmd/rekor-server/app/root.go b/cmd/rekor-server/app/root.go index a09b31b29..e258c2aa5 100644 --- a/cmd/rekor-server/app/root.go +++ b/cmd/rekor-server/app/root.go @@ -95,7 +95,7 @@ Memory and file-based signers should only be used for testing.`) rootCmd.PersistentFlags().Uint16("port", 3000, "Port to bind to") rootCmd.PersistentFlags().Bool("enable_retrieve_api", true, "enables Redis-based index API endpoint") - rootCmd.PersistentFlags().MarkDeprecated("enable_retrieve_api", "this flag is deprecated in favor of enabled_api_endpoints (searchIndex)") + _ = rootCmd.PersistentFlags().MarkDeprecated("enable_retrieve_api", "this flag is deprecated in favor of enabled_api_endpoints (searchIndex)") rootCmd.PersistentFlags().String("redis_server.address", "127.0.0.1", "Redis server address") rootCmd.PersistentFlags().Uint16("redis_server.port", 6379, "Redis server port") diff --git a/go.mod b/go.mod index efb0c5a72..1f3fb05f5 100644 --- a/go.mod +++ b/go.mod @@ -37,7 +37,6 @@ require ( github.com/tent/canonical-json-go v0.0.0-20130607151641-96e4ba3a7613 github.com/theupdateframework/go-tuf v0.5.1-0.20220920170306-f237d7ca5b42 github.com/transparency-dev/merkle v0.0.1 - github.com/urfave/negroni v1.0.0 github.com/veraison/go-cose v1.0.0-rc.1 github.com/zalando/go-keyring v0.1.1 // indirect go.uber.org/goleak v1.2.0 diff --git a/go.sum b/go.sum index f3a62ac38..75623063c 100644 --- a/go.sum +++ b/go.sum @@ -780,8 +780,6 @@ github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLY github.com/ulikunitz/xz v0.5.7/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8= github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc= -github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= github.com/veraison/go-cose v1.0.0-rc.1 h1:4qA7dbFJGvt7gcqv5MCIyCQvN+NpHFPkW7do3EeDLb8= github.com/veraison/go-cose v1.0.0-rc.1/go.mod h1:7ziE85vSq4ScFTg6wyoMXjucIGOf4JkFEZi/an96Ct4= github.com/vmihailenco/msgpack/v4 v4.3.12 h1:07s4sz9IReOgdikxLTKNbBdqDMLsjPKXwvCazn8G65U= diff --git a/pkg/generated/restapi/configure_rekor_server.go b/pkg/generated/restapi/configure_rekor_server.go index c9d9dc6b9..30342fe30 100644 --- a/pkg/generated/restapi/configure_rekor_server.go +++ b/pkg/generated/restapi/configure_rekor_server.go @@ -93,14 +93,14 @@ func configureAPI(api *operations.RekorServerAPI) http.Handler { api.PubkeyGetPublicKeyHandler = pubkey.GetPublicKeyHandlerFunc(pkgapi.GetPublicKeyNotImplementedHandler) api.TlogGetLogProofHandler = tlog.GetLogProofHandlerFunc(pkgapi.GetLogProofNotImplementedHandler) - enabled_api_endpoints := viper.GetStringSlice("enabled_api_endpoints") - if !slices.Contains(enabled_api_endpoints, "searchIndex") && viper.GetBool("enable_retrieve_api") { - enabled_api_endpoints = append(enabled_api_endpoints, "searchIndex") + enabledAPIEndpoints := viper.GetStringSlice("enabled_api_endpoints") + if !slices.Contains(enabledAPIEndpoints, "searchIndex") && viper.GetBool("enable_retrieve_api") { + enabledAPIEndpoints = append(enabledAPIEndpoints, "searchIndex") } - for _, enabled_api := range enabled_api_endpoints { - log.Logger.Infof("Enabling API endpoint: %s", enabled_api) - switch enabled_api { + for _, enabledAPI := range enabledAPIEndpoints { + log.Logger.Infof("Enabling API endpoint: %s", enabledAPI) + switch enabledAPI { case "searchIndex": api.IndexSearchIndexHandler = index.SearchIndexHandlerFunc(pkgapi.SearchIndexHandler) case "getLogInfo": @@ -118,13 +118,13 @@ func configureAPI(api *operations.RekorServerAPI) http.Handler { case "searchLogQuery": api.EntriesSearchLogQueryHandler = entries.SearchLogQueryHandlerFunc(pkgapi.SearchLogQueryHandler) default: - log.Logger.Panicf("Unknown API endpoint requested: %s", enabled_api) + log.Logger.Panicf("Unknown API endpoint requested: %s", enabledAPI) } } // all handlers need to be set before a call to api.AddMiddlewareFor - for _, enabled_api := range enabled_api_endpoints { - switch enabled_api { + for _, enabledAPI := range enabledAPIEndpoints { + switch enabledAPI { case "searchIndex": recordMetricsForAPI(api, "POST", "/api/v1/index/retrieve") // add metrics case "getLogInfo":