diff --git a/Dockerfile b/Dockerfile index e41aa4652..bb4e144b7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,7 +19,8 @@ COPY --from=build /go/src/istio.io/fortio/ui/templates /usr/local/lib/fortio/tem COPY --from=build /go/src/istio.io/fortio.bin /usr/local/bin/fortio EXPOSE 8079 EXPOSE 8080 +EXPOSE 8081 VOLUME /var/lib/istio/fortio ENTRYPOINT ["/usr/local/bin/fortio"] -# start the server mode (grpc ping on 8079, http echo and UI on 8080) by default +# start the server mode (grpc ping on 8079, http echo and UI on 8080, redirector on 8081) by default CMD ["server"] diff --git a/fortio_main.go b/fortio_main.go index 1761742e1..b138d6db0 100644 --- a/fortio_main.go +++ b/fortio_main.go @@ -105,6 +105,8 @@ var ( allowInitialErrorsFlag = flag.Bool("allow-initial-errors", false, "Allow and don't abort on initial warmup errors") autoSaveFlag = flag.Bool("a", false, "Automatically save JSON result with filename based on labels and timestamp") + redirectFlag = flag.Int("redirect-port", 8081, + "Redirect all incoming traffic to https URL (need ingress to work properly). -1 means off.") ) func main() { @@ -133,8 +135,14 @@ func main() { case "load": fortioLoad() case "report": + if *redirectFlag >= 0 { + go ui.RedirectToHTTPS(*redirectFlag) + } ui.Report(*echoPortFlag, *staticDirFlag, *dataDirFlag) case "server": + if *redirectFlag >= 0 { + go ui.RedirectToHTTPS(*redirectFlag) + } go ui.Serve(*echoPortFlag, *echoDbgPathFlag, *uiPathFlag, *staticDirFlag, *dataDirFlag) pingServer(*grpcPortFlag) case "grpcping": diff --git a/periodic/periodic.go b/periodic/periodic.go index 4d62ba088..dc74ee62f 100644 --- a/periodic/periodic.go +++ b/periodic/periodic.go @@ -37,7 +37,7 @@ import ( const ( // Version is the overall package version (used to version json output too). - Version = "0.6.0" + Version = "0.6.1" ) // DefaultRunnerOptions are the default values for options (do not mutate!). diff --git a/ui/uihandler.go b/ui/uihandler.go index 0074617fa..da66ec415 100644 --- a/ui/uihandler.go +++ b/ui/uihandler.go @@ -41,6 +41,9 @@ import ( "istio.io/fortio/stats" ) +// TODO: move some of those in their own files/package (e.g data transfer TSV) +// and add unit tests. + var ( // UI and Debug prefix/paths (read in ui handler). uiPath string // absolute (base) @@ -386,7 +389,8 @@ func BrowseHandler(w http.ResponseWriter, r *http.Request) { // LogRequest logs the incoming request, including headers when loglevel is verbose func LogRequest(r *http.Request, msg string) { - log.Infof("%s: %v %v %v %v", msg, r.Method, r.URL, r.Proto, r.RemoteAddr) + log.Infof("%s: %v %v %v %v (%s)", msg, r.Method, r.URL, r.Proto, r.RemoteAddr, + r.Header.Get("X-Forwarded-Proto")) if log.LogVerbose() { for name, headers := range r.Header { for _, h := range headers { @@ -470,9 +474,13 @@ func sendTSVDataIndex(urlPrefix string, w http.ResponseWriter) { gTSVCache.cachedResult = b.Bytes() } result := gTSVCache.cachedResult + lastModified := gTSVCache.cachedDirTime.Format(http.TimeFormat) gTSVCacheMutex.Unlock() log.Infof("Used cached %v to serve %d bytes TSV", useCache, len(result)) w.Header().Set("Content-Type", "text/plain; charset=UTF-8") + // Cloud transfer requires ETag + w.Header().Set("ETag", fmt.Sprintf("\"%s\"", lastModified)) + w.Header().Set("Last-Modified", lastModified) w.Write(result) // nolint: errcheck, gas } @@ -488,8 +496,13 @@ func LogAndFilterDataRequest(h http.Handler) http.Handler { w.Header().Set("Access-Control-Allow-Origin", "*") ext := "/index.tsv" if strings.HasSuffix(path, ext) { - // TODO: what if we are reached through https ingress? or a different port - urlPrefix := "http://" + r.Host + path[:len(path)-len(ext)+1] + // Ingress effect: + // The Host header includes original host/port, only missing is the proto: + proto := r.Header.Get("X-Forwarded-Proto") + if len(proto) == 0 { + proto = "http" + } + urlPrefix := proto + "://" + r.Host + path[:len(path)-len(ext)+1] log.LogVf("Prefix is '%s'", urlPrefix) sendTSVDataIndex(urlPrefix, w) return @@ -612,6 +625,31 @@ func Report(port int, staticRsrcDir string, datadir string) { fsd := http.FileServer(http.Dir(dataDir)) http.Handle(uiPath+"data/", LogAndFilterDataRequest(http.StripPrefix(uiPath+"data", fsd))) if err := http.ListenAndServe(fmt.Sprintf(":%d", port), nil); err != nil { - log.Critf("Error starting server: %v", err) + log.Critf("Error starting report server: %v", err) + } +} + +// -- Redirection to https feature -- + +// RedirectToHTTPSHandler handler sends a redirect to same URL with https. +func RedirectToHTTPSHandler(w http.ResponseWriter, r *http.Request) { + dest := "https://" + r.Host + r.URL.String() + LogRequest(r, "Redirecting to "+dest) + http.Redirect(w, r, dest, http.StatusSeeOther) +} + +// RedirectToHTTPS Sets up a redirector to https on the given port. +// (Do not create a loop, make sure this is addressed from an ingress) +func RedirectToHTTPS(port int) { + m := http.NewServeMux() + m.HandleFunc("/", RedirectToHTTPSHandler) + s := &http.Server{ + Addr: fmt.Sprintf(":%d", port), + Handler: m, + } + fmt.Printf("Https redirector running on %v\n", s.Addr) + if err := s.ListenAndServe(); err != nil { + log.Critf("Error starting report server: %v", err) } + fmt.Printf("Not reached, https redirector exiting - was on %v\n", s.Addr) }