From 0650dd7171db4d036b662923cca653636ca116e5 Mon Sep 17 00:00:00 2001 From: Michael Schoebel Date: Wed, 6 May 2015 22:44:37 +0200 Subject: [PATCH 1/3] New internal middleware --- config/directives.go | 1 + config/setup/internal.go | 31 ++++++++++++ middleware/internal/internal.go | 88 +++++++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+) create mode 100644 config/setup/internal.go create mode 100644 middleware/internal/internal.go diff --git a/config/directives.go b/config/directives.go index fe9a9f929..9db5df447 100644 --- a/config/directives.go +++ b/config/directives.go @@ -59,6 +59,7 @@ var directiveOrder = []directive{ {"redir", setup.Redir}, {"ext", setup.Ext}, {"basicauth", setup.BasicAuth}, + {"internal", setup.Internal}, {"proxy", setup.Proxy}, {"fastcgi", setup.FastCGI}, {"websocket", setup.WebSocket}, diff --git a/config/setup/internal.go b/config/setup/internal.go new file mode 100644 index 000000000..21ab71b6b --- /dev/null +++ b/config/setup/internal.go @@ -0,0 +1,31 @@ +package setup + +import ( + "github.com/mholt/caddy/middleware" + "github.com/mholt/caddy/middleware/internal" +) + +// Internal configures a new Internal middleware instance. +func Internal(c *Controller) (middleware.Middleware, error) { + paths, err := internalParse(c) + if err != nil { + return nil, err + } + + return func(next middleware.Handler) middleware.Handler { + return internal.Internal{Next: next, Paths: paths} + }, nil +} + +func internalParse(c *Controller) ([]string, error) { + var paths []string + + for c.Next() { + if !c.NextArg() { + return paths, c.ArgErr() + } + paths = append(paths, c.Val()) + } + + return paths, nil +} diff --git a/middleware/internal/internal.go b/middleware/internal/internal.go new file mode 100644 index 000000000..67af49412 --- /dev/null +++ b/middleware/internal/internal.go @@ -0,0 +1,88 @@ +// The package internal provides a simple middleware that (a) prevents access +// to internal locations and (b) allows to return files from internal location +// by setting a special header, e.g. in a proxy response. +package internal + +import ( + "net/http" + + "github.com/mholt/caddy/middleware" +) + +// Internal middleware protects internal locations from external requests - +// but allows access from the inside by using a special HTTP header. +type Internal struct { + Next middleware.Handler + Paths []string +} + +const redirectHeader string = "X-Accel-Redirect" + +func isInternalRedirect(w http.ResponseWriter) bool { + return w.Header().Get(redirectHeader) != "" +} + +// ServeHTTP implements the middlware.Handler interface. +func (i Internal) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { + + // Internal location requested? -> Not found. + for _, prefix := range i.Paths { + if middleware.Path(r.URL.Path).Matches(prefix) { + return http.StatusNotFound, nil + } + } + + // Use internal response writer to ignore responses that will be + // redirected to internal locations + iw := internalResponseWriter{ResponseWriter: w} + status, err := i.Next.ServeHTTP(iw, r) + + if isInternalRedirect(iw) && status < 400 { + // Redirect - adapt request URL path and send it again + // "down the chain" + r.URL.Path = iw.Header().Get(redirectHeader) + iw.ClearHeader() + + status, err = i.Next.ServeHTTP(iw, r) + + if isInternalRedirect(iw) { + // multiple redirects not supported + iw.ClearHeader() + return http.StatusInternalServerError, nil + } + } + + return status, err +} + +// internalResponseWriter wraps the underlying http.ResponseWriter and ignores +// calls to Write and WriteHeader if the response should be redirected to an +// internal location. +type internalResponseWriter struct { + http.ResponseWriter +} + +// ClearHeader removes all header fields that are already set. +func (w internalResponseWriter) ClearHeader() { + for k := range w.Header() { + w.Header().Del(k) + } +} + +// WriteHeader ignores the call if the response should be redirected to an +// internal location. +func (w internalResponseWriter) WriteHeader(code int) { + if !isInternalRedirect(w) && code < 400 { + w.ResponseWriter.WriteHeader(code) + } +} + +// Write ignores the call if the response should be redirected to an internal +// location. +func (w internalResponseWriter) Write(b []byte) (int, error) { + if isInternalRedirect(w) { + return 0, nil + } else { + return w.ResponseWriter.Write(b) + } +} From a5b565e1938652fcb38ce115f1ac0b90fb046e68 Mon Sep 17 00:00:00 2001 From: Michael Schoebel Date: Thu, 7 May 2015 20:20:45 +0200 Subject: [PATCH 2/3] Adapted internal middleware - redirect internally regardless of proxy status code - support multiple internal redirects --- middleware/internal/internal.go | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/middleware/internal/internal.go b/middleware/internal/internal.go index 67af49412..7678ee0a3 100644 --- a/middleware/internal/internal.go +++ b/middleware/internal/internal.go @@ -37,19 +37,13 @@ func (i Internal) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) iw := internalResponseWriter{ResponseWriter: w} status, err := i.Next.ServeHTTP(iw, r) - if isInternalRedirect(iw) && status < 400 { + for isInternalRedirect(iw) { // Redirect - adapt request URL path and send it again // "down the chain" r.URL.Path = iw.Header().Get(redirectHeader) iw.ClearHeader() status, err = i.Next.ServeHTTP(iw, r) - - if isInternalRedirect(iw) { - // multiple redirects not supported - iw.ClearHeader() - return http.StatusInternalServerError, nil - } } return status, err @@ -72,7 +66,7 @@ func (w internalResponseWriter) ClearHeader() { // WriteHeader ignores the call if the response should be redirected to an // internal location. func (w internalResponseWriter) WriteHeader(code int) { - if !isInternalRedirect(w) && code < 400 { + if !isInternalRedirect(w) { w.ResponseWriter.WriteHeader(code) } } From e3d64169ed4921d1d8d4a65418a0b0653573dbe1 Mon Sep 17 00:00:00 2001 From: Michael Schoebel Date: Thu, 7 May 2015 20:48:29 +0200 Subject: [PATCH 3/3] Adapted internal middleware - Detect too many internal redirects - return 500 in this case --- middleware/internal/internal.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/middleware/internal/internal.go b/middleware/internal/internal.go index 7678ee0a3..90746b063 100644 --- a/middleware/internal/internal.go +++ b/middleware/internal/internal.go @@ -16,7 +16,10 @@ type Internal struct { Paths []string } -const redirectHeader string = "X-Accel-Redirect" +const ( + redirectHeader string = "X-Accel-Redirect" + maxRedirectCount int = 10 +) func isInternalRedirect(w http.ResponseWriter) bool { return w.Header().Get(redirectHeader) != "" @@ -37,7 +40,7 @@ func (i Internal) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) iw := internalResponseWriter{ResponseWriter: w} status, err := i.Next.ServeHTTP(iw, r) - for isInternalRedirect(iw) { + for c := 0; c < maxRedirectCount && isInternalRedirect(iw); c++ { // Redirect - adapt request URL path and send it again // "down the chain" r.URL.Path = iw.Header().Get(redirectHeader) @@ -46,6 +49,12 @@ func (i Internal) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) status, err = i.Next.ServeHTTP(iw, r) } + if isInternalRedirect(iw) { + // Too many redirect cycles + iw.ClearHeader() + return http.StatusInternalServerError, nil + } + return status, err }