From 740f7690ea8f5c078512babca0acc5c0a6778e7c Mon Sep 17 00:00:00 2001 From: Dmitry Savintsev Date: Mon, 11 Sep 2023 18:33:14 +0200 Subject: [PATCH] Add command-line flag --http-ping-only New optional command-line flag --http-ping-only allows to enforce that the http server (which doesn't support mTLS) allows only to access the /ping entrypoint. This would limit the risk of the timestamp server being accessed without mTLS in case of the strict mTLS requirement while still allowing the mTLS-exempt /ping entrypoint to be called for example by the heartbeat checkers (load balancers etc.). Fixes #420. Signed-off-by: Dmitry Savintsev --- cmd/timestamp-server/app/root.go | 9 ++++---- cmd/timestamp-server/app/serve.go | 4 ++-- .../restapi/configure_timestamp_server.go | 11 +++++---- pkg/internal/cmdparams/cmdparams.go | 23 +++++++++++++++++++ pkg/internal/cmdparams/doc.go | 19 +++++++++++++++ pkg/ntpmonitor/config.go | 1 - pkg/server/restapi.go | 9 ++++++-- pkg/tests/server.go | 2 +- 8 files changed, 64 insertions(+), 14 deletions(-) create mode 100644 pkg/internal/cmdparams/cmdparams.go create mode 100644 pkg/internal/cmdparams/doc.go diff --git a/cmd/timestamp-server/app/root.go b/cmd/timestamp-server/app/root.go index 333d0b533..971d5508e 100644 --- a/cmd/timestamp-server/app/root.go +++ b/cmd/timestamp-server/app/root.go @@ -26,9 +26,10 @@ import ( ) var ( - cfgFile string - logType string - enablePprof bool + cfgFile string + logType string + enablePprof bool + httpPingOnly bool ) // rootCmd represents the base command when called without any subcommands @@ -56,7 +57,7 @@ func init() { rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.timestamp-server.yaml)") rootCmd.PersistentFlags().StringVar(&logType, "log-type", "dev", "logger type to use (dev/prod)") rootCmd.PersistentFlags().BoolVar(&enablePprof, "enable-pprof", false, "enable pprof for profiling on port 6060") - + rootCmd.PersistentFlags().BoolVar(&httpPingOnly, "http-ping-only", false, "serve only /ping in the http server") rootCmd.PersistentFlags().String("timestamp-signer", "memory", "Timestamping authority signer. Valid options include: [kms, tink, memory, file]. Memory and file-based signers should only be used for testing") // KMS flags rootCmd.PersistentFlags().String("kms-key-resource", "", "KMS key for signing timestamp responses. Valid options include: [gcpkms://resource, azurekms://resource, hashivault://resource, awskms://resource]") diff --git a/cmd/timestamp-server/app/serve.go b/cmd/timestamp-server/app/serve.go index 9d432a267..a7807b5e0 100644 --- a/cmd/timestamp-server/app/serve.go +++ b/cmd/timestamp-server/app/serve.go @@ -100,8 +100,8 @@ var serveCmd = &cobra.Command{ host := viper.GetString("host") port := int(viper.GetUint("port")) scheme := viper.GetStringSlice("scheme") - - server := server.NewRestAPIServer(host, port, scheme, readTimeout, writeTimeout) + httpPingOnly := viper.GetBool("http-ping-only") + server := server.NewRestAPIServer(host, port, scheme, httpPingOnly, readTimeout, writeTimeout) defer func() { if err := server.Shutdown(); err != nil { log.Logger.Error(err) diff --git a/pkg/generated/restapi/configure_timestamp_server.go b/pkg/generated/restapi/configure_timestamp_server.go index f99a659ae..da6474520 100644 --- a/pkg/generated/restapi/configure_timestamp_server.go +++ b/pkg/generated/restapi/configure_timestamp_server.go @@ -34,6 +34,7 @@ import ( pkgapi "github.com/sigstore/timestamp-authority/pkg/api" "github.com/sigstore/timestamp-authority/pkg/generated/restapi/operations" "github.com/sigstore/timestamp-authority/pkg/generated/restapi/operations/timestamp" + "github.com/sigstore/timestamp-authority/pkg/internal/cmdparams" "github.com/sigstore/timestamp-authority/pkg/log" ) @@ -102,15 +103,15 @@ func (l *logAdapter) Print(v ...interface{}) { log.Logger.Info(v...) } -// httpPingOnly custom middleware prohibits all entrypoing except +// httpPingOnly custom middleware prohibits all entrypoints except // "/ping" on the http (non-HTTPS) server. func httpPingOnly(endpoint string) func(http.Handler) http.Handler { f := func(h http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { if r.URL.Scheme != "https" && !strings.EqualFold(r.URL.Path, endpoint) { w.Header().Set("Content-Type", "text/plain") - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("http server supports only the /ping entrypoint")) + w.WriteHeader(http.StatusForbidden) + w.Write([]byte("http server supports only the /ping entrypoint")) //nolint:errcheck return } h.ServeHTTP(w, r) @@ -128,7 +129,9 @@ func setupGlobalMiddleware(handler http.Handler) http.Handler { returnHandler := middleware.Logger(handler) returnHandler = middleware.Recoverer(returnHandler) returnHandler = middleware.Heartbeat("/ping")(returnHandler) - returnHandler = httpPingOnly("/ping")(returnHandler) + if cmdparams.IsHTTPPingOnly { + returnHandler = httpPingOnly("/ping")(returnHandler) + } handleCORS := cors.Default().Handler returnHandler = handleCORS(returnHandler) diff --git a/pkg/internal/cmdparams/cmdparams.go b/pkg/internal/cmdparams/cmdparams.go new file mode 100644 index 000000000..e19b5d394 --- /dev/null +++ b/pkg/internal/cmdparams/cmdparams.go @@ -0,0 +1,23 @@ +// +// Copyright 2022 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmdparams + +// IsHTTPPingOnly is set off the command-line flag to enforce limiting +// the non-mTLS http server to only serving the /ping entrypoint. +// It should be set only once when processing command-line flags +// and then used only in pkg/generated/restapi/configure_timestamp_server.go +// and as read-only. +var IsHTTPPingOnly bool diff --git a/pkg/internal/cmdparams/doc.go b/pkg/internal/cmdparams/doc.go new file mode 100644 index 000000000..b068e810f --- /dev/null +++ b/pkg/internal/cmdparams/doc.go @@ -0,0 +1,19 @@ +// +// Copyright 2022 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package cmdparams contains variables to propagate from command-line +// flags to their handling in +// pkg/generated/restapi/configure_timestamp_server.go. +package cmdparams diff --git a/pkg/ntpmonitor/config.go b/pkg/ntpmonitor/config.go index 67e8b9265..9794b21d2 100644 --- a/pkg/ntpmonitor/config.go +++ b/pkg/ntpmonitor/config.go @@ -1,4 +1,3 @@ -// // Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/pkg/server/restapi.go b/pkg/server/restapi.go index 0e3131737..e7206f82a 100644 --- a/pkg/server/restapi.go +++ b/pkg/server/restapi.go @@ -21,10 +21,15 @@ import ( "github.com/sigstore/timestamp-authority/pkg/api" "github.com/sigstore/timestamp-authority/pkg/generated/restapi" "github.com/sigstore/timestamp-authority/pkg/generated/restapi/operations" + "github.com/sigstore/timestamp-authority/pkg/internal/cmdparams" ) // NewRestAPIServer creates a server for serving the rest API TSA service -func NewRestAPIServer(host string, port int, scheme []string, readTimeout, writeTimeout time.Duration) *restapi.Server { +func NewRestAPIServer(host string, + port int, + scheme []string, + httpReadOnly bool, + readTimeout, writeTimeout time.Duration) *restapi.Server { doc, _ := loads.Embedded(restapi.SwaggerJSON, restapi.FlatSwaggerJSON) server := restapi.NewServer(operations.NewTimestampServerAPI(doc)) @@ -33,7 +38,7 @@ func NewRestAPIServer(host string, port int, scheme []string, readTimeout, write server.EnabledListeners = scheme server.ReadTimeout = readTimeout server.WriteTimeout = writeTimeout - + cmdparams.IsHTTPPingOnly = httpReadOnly api.ConfigureAPI() server.ConfigureAPI() diff --git a/pkg/tests/server.go b/pkg/tests/server.go index d470fad95..9e92302f5 100644 --- a/pkg/tests/server.go +++ b/pkg/tests/server.go @@ -29,7 +29,7 @@ import ( func createServer(t *testing.T) string { viper.Set("timestamp-signer", "memory") // unused port - apiServer := server.NewRestAPIServer("localhost", 0, []string{"http"}, 10*time.Second, 10*time.Second) + apiServer := server.NewRestAPIServer("localhost", 0, []string{"http"}, false, 10*time.Second, 10*time.Second) server := httptest.NewServer(apiServer.GetHandler()) t.Cleanup(server.Close)