You've already forked oauth2-proxy
mirror of
https://github.com/oauth2-proxy/oauth2-proxy.git
synced 2025-11-06 08:59:21 +02:00
Request ID Logging (#1087)
* Add RequestID to the RequestScope
* Expose RequestID to auth & request loggers
* Use the RequestID in templated HTML pages
* Allow customizing the RequestID header
* Document new Request ID support
* Add more cases to scope/requestID tests
* Split Get vs Generate RequestID funtionality
* Add {{.RequestID}} to the request logger tests
* Move RequestID management to RequestScope
* Use HTML escape instead of sanitization for Request ID rendering
This commit is contained in:
@@ -21,6 +21,10 @@ type RequestScope struct {
|
||||
// mode and if request `X-Forwarded-*` headers should be trusted
|
||||
ReverseProxy bool
|
||||
|
||||
// RequestID is set to the request's `X-Request-Id` header if set.
|
||||
// Otherwise a random UUID is set.
|
||||
RequestID string
|
||||
|
||||
// Session details the authenticated users information (if it exists).
|
||||
Session *sessions.SessionState
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ type Logging struct {
|
||||
ExcludePaths []string `flag:"exclude-logging-path" cfg:"exclude_logging_paths"`
|
||||
LocalTime bool `flag:"logging-local-time" cfg:"logging_local_time"`
|
||||
SilencePing bool `flag:"silence-ping-logging" cfg:"silence_ping_logging"`
|
||||
RequestIDHeader string `flag:"request-id-header" cfg:"request_id_header"`
|
||||
File LogFileOptions `cfg:",squash"`
|
||||
}
|
||||
|
||||
@@ -43,6 +44,7 @@ func loggingFlagSet() *pflag.FlagSet {
|
||||
flagSet.StringSlice("exclude-logging-path", []string{}, "Exclude logging requests to paths (eg: '/path1,/path2,/path3')")
|
||||
flagSet.Bool("logging-local-time", true, "If the time in log files and backup filenames are local or UTC time")
|
||||
flagSet.Bool("silence-ping-logging", false, "Disable logging of requests to ping endpoint")
|
||||
flagSet.String("request-id-header", "X-Request-Id", "Request header to use as the request ID")
|
||||
|
||||
flagSet.String("logging-filename", "", "File to log requests to, empty for stdout")
|
||||
flagSet.Int("logging-max-size", 100, "Maximum size in megabytes of the log file before rotation")
|
||||
@@ -59,6 +61,7 @@ func loggingDefaults() Logging {
|
||||
ExcludePaths: nil,
|
||||
LocalTime: true,
|
||||
SilencePing: false,
|
||||
RequestIDHeader: "X-Request-Id",
|
||||
AuthEnabled: true,
|
||||
AuthFormat: logger.DefaultAuthLoggingFormat,
|
||||
RequestEnabled: true,
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
<h1 class="subtitle is-1">{{.Title}}</h1>
|
||||
</div>
|
||||
|
||||
{{ if .Message }}
|
||||
{{ if or .Message .RequestID }}
|
||||
<div id="more-info" class="block card is-fullwidth is-shadowless">
|
||||
<header class="card-header is-shadowless">
|
||||
<p class="card-header-title">More Info</p>
|
||||
@@ -56,9 +56,16 @@
|
||||
</a>
|
||||
</header>
|
||||
<div class="card-content has-text-left is-hidden">
|
||||
{{ if .Message }}
|
||||
<div class="content">
|
||||
{{.Message}}
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ if .RequestID }}
|
||||
<div class="content">
|
||||
Request ID: {{.RequestID}}
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"html/template"
|
||||
"net/http"
|
||||
|
||||
middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
|
||||
)
|
||||
|
||||
@@ -37,11 +38,25 @@ type errorPageWriter struct {
|
||||
debug bool
|
||||
}
|
||||
|
||||
// ErrorPageOpts bundles up all the content needed to write the Error Page
|
||||
type ErrorPageOpts struct {
|
||||
// HTTP status code
|
||||
Status int
|
||||
// Redirect URL for "Go back" and "Sign in" buttons
|
||||
RedirectURL string
|
||||
// The UUID of the request
|
||||
RequestID string
|
||||
// App Error shown in debug mode
|
||||
AppError string
|
||||
// Generic error messages shown in non-debug mode
|
||||
Messages []interface{}
|
||||
}
|
||||
|
||||
// WriteErrorPage writes an error page to the given response writer.
|
||||
// It uses the passed redirectURL to give users the option to go back to where
|
||||
// they originally came from or try signing in again.
|
||||
func (e *errorPageWriter) WriteErrorPage(rw http.ResponseWriter, status int, redirectURL string, appError string, messages ...interface{}) {
|
||||
rw.WriteHeader(status)
|
||||
func (e *errorPageWriter) WriteErrorPage(rw http.ResponseWriter, opts ErrorPageOpts) {
|
||||
rw.WriteHeader(opts.Status)
|
||||
|
||||
// We allow unescaped template.HTML since it is user configured options
|
||||
/* #nosec G203 */
|
||||
@@ -51,14 +66,16 @@ func (e *errorPageWriter) WriteErrorPage(rw http.ResponseWriter, status int, red
|
||||
ProxyPrefix string
|
||||
StatusCode int
|
||||
Redirect string
|
||||
RequestID string
|
||||
Footer template.HTML
|
||||
Version string
|
||||
}{
|
||||
Title: http.StatusText(status),
|
||||
Message: e.getMessage(status, appError, messages...),
|
||||
Title: http.StatusText(opts.Status),
|
||||
Message: e.getMessage(opts.Status, opts.AppError, opts.Messages...),
|
||||
ProxyPrefix: e.proxyPrefix,
|
||||
StatusCode: status,
|
||||
Redirect: redirectURL,
|
||||
StatusCode: opts.Status,
|
||||
Redirect: opts.RedirectURL,
|
||||
RequestID: opts.RequestID,
|
||||
Footer: template.HTML(e.footer),
|
||||
Version: e.version,
|
||||
}
|
||||
@@ -74,7 +91,14 @@ func (e *errorPageWriter) WriteErrorPage(rw http.ResponseWriter, status int, red
|
||||
// It is expected to always render a bad gateway error.
|
||||
func (e *errorPageWriter) ProxyErrorHandler(rw http.ResponseWriter, req *http.Request, proxyErr error) {
|
||||
logger.Errorf("Error proxying to upstream server: %v", proxyErr)
|
||||
e.WriteErrorPage(rw, http.StatusBadGateway, "", proxyErr.Error(), "There was a problem connecting to the upstream server.")
|
||||
scope := middlewareapi.GetRequestScope(req)
|
||||
e.WriteErrorPage(rw, ErrorPageOpts{
|
||||
Status: http.StatusBadGateway,
|
||||
RedirectURL: "", // The user is already logged in and has hit an upstream error. Makes no sense to redirect in this case.
|
||||
RequestID: scope.RequestID,
|
||||
AppError: proxyErr.Error(),
|
||||
Messages: []interface{}{"There was a problem connecting to the upstream server."},
|
||||
})
|
||||
}
|
||||
|
||||
// getMessage creates the message for the template parameters.
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"io/ioutil"
|
||||
"net/http/httptest"
|
||||
|
||||
middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
@@ -14,7 +15,7 @@ var _ = Describe("Error Page Writer", func() {
|
||||
var errorPage *errorPageWriter
|
||||
|
||||
BeforeEach(func() {
|
||||
tmpl, err := template.New("").Parse("{{.Title}} {{.Message}} {{.ProxyPrefix}} {{.StatusCode}} {{.Redirect}} {{.Footer}} {{.Version}}")
|
||||
tmpl, err := template.New("").Parse("{{.Title}} {{.Message}} {{.ProxyPrefix}} {{.StatusCode}} {{.Redirect}} {{.RequestID}} {{.Footer}} {{.Version}}")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
errorPage = &errorPageWriter{
|
||||
@@ -28,41 +29,77 @@ var _ = Describe("Error Page Writer", func() {
|
||||
Context("WriteErrorPage", func() {
|
||||
It("Writes the template to the response writer", func() {
|
||||
recorder := httptest.NewRecorder()
|
||||
errorPage.WriteErrorPage(recorder, 403, "/redirect", "Access Denied")
|
||||
errorPage.WriteErrorPage(recorder, ErrorPageOpts{
|
||||
Status: 403,
|
||||
RedirectURL: "/redirect",
|
||||
RequestID: testRequestID,
|
||||
AppError: "Access Denied",
|
||||
})
|
||||
|
||||
body, err := ioutil.ReadAll(recorder.Result().Body)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(string(body)).To(Equal("Forbidden You do not have permission to access this resource. /prefix/ 403 /redirect Custom Footer Text v0.0.0-test"))
|
||||
Expect(string(body)).To(Equal("Forbidden You do not have permission to access this resource. /prefix/ 403 /redirect 11111111-2222-4333-8444-555555555555 Custom Footer Text v0.0.0-test"))
|
||||
})
|
||||
|
||||
It("With a different code, uses the stock message for the correct code", func() {
|
||||
recorder := httptest.NewRecorder()
|
||||
errorPage.WriteErrorPage(recorder, 500, "/redirect", "Access Denied")
|
||||
errorPage.WriteErrorPage(recorder, ErrorPageOpts{
|
||||
Status: 500,
|
||||
RedirectURL: "/redirect",
|
||||
RequestID: testRequestID,
|
||||
AppError: "Access Denied",
|
||||
})
|
||||
|
||||
body, err := ioutil.ReadAll(recorder.Result().Body)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(string(body)).To(Equal("Internal Server Error Oops! Something went wrong. For more information contact your server administrator. /prefix/ 500 /redirect Custom Footer Text v0.0.0-test"))
|
||||
Expect(string(body)).To(Equal("Internal Server Error Oops! Something went wrong. For more information contact your server administrator. /prefix/ 500 /redirect 11111111-2222-4333-8444-555555555555 Custom Footer Text v0.0.0-test"))
|
||||
})
|
||||
|
||||
It("With a message override, uses the message", func() {
|
||||
recorder := httptest.NewRecorder()
|
||||
errorPage.WriteErrorPage(recorder, 403, "/redirect", "Access Denied", "An extra message: %s", "with more context.")
|
||||
errorPage.WriteErrorPage(recorder, ErrorPageOpts{
|
||||
Status: 403,
|
||||
RedirectURL: "/redirect",
|
||||
RequestID: testRequestID,
|
||||
AppError: "Access Denied",
|
||||
Messages: []interface{}{
|
||||
"An extra message: %s",
|
||||
"with more context.",
|
||||
},
|
||||
})
|
||||
|
||||
body, err := ioutil.ReadAll(recorder.Result().Body)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(string(body)).To(Equal("Forbidden An extra message: with more context. /prefix/ 403 /redirect Custom Footer Text v0.0.0-test"))
|
||||
Expect(string(body)).To(Equal("Forbidden An extra message: with more context. /prefix/ 403 /redirect 11111111-2222-4333-8444-555555555555 Custom Footer Text v0.0.0-test"))
|
||||
})
|
||||
|
||||
It("Sanitizes malicious user input", func() {
|
||||
recorder := httptest.NewRecorder()
|
||||
errorPage.WriteErrorPage(recorder, ErrorPageOpts{
|
||||
Status: 403,
|
||||
RedirectURL: "/redirect",
|
||||
RequestID: "<script>alert(1)</script>",
|
||||
AppError: "Access Denied",
|
||||
})
|
||||
|
||||
body, err := ioutil.ReadAll(recorder.Result().Body)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(string(body)).To(Equal("Forbidden You do not have permission to access this resource. /prefix/ 403 /redirect <script>alert(1)</script> Custom Footer Text v0.0.0-test"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("ProxyErrorHandler", func() {
|
||||
It("Writes a bad gateway error the response writer", func() {
|
||||
req := httptest.NewRequest("", "/bad-gateway", nil)
|
||||
req = middlewareapi.AddRequestScope(req, &middlewareapi.RequestScope{
|
||||
RequestID: testRequestID,
|
||||
})
|
||||
recorder := httptest.NewRecorder()
|
||||
errorPage.ProxyErrorHandler(recorder, req, errors.New("some upstream error"))
|
||||
|
||||
body, err := ioutil.ReadAll(recorder.Result().Body)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(string(body)).To(Equal("Bad Gateway There was a problem connecting to the upstream server. /prefix/ 502 Custom Footer Text v0.0.0-test"))
|
||||
Expect(string(body)).To(Equal("Bad Gateway There was a problem connecting to the upstream server. /prefix/ 502 11111111-2222-4333-8444-555555555555 Custom Footer Text v0.0.0-test"))
|
||||
})
|
||||
})
|
||||
|
||||
@@ -78,7 +115,11 @@ var _ = Describe("Error Page Writer", func() {
|
||||
Context("WriteErrorPage", func() {
|
||||
It("Writes the detailed error in place of the message", func() {
|
||||
recorder := httptest.NewRecorder()
|
||||
errorPage.WriteErrorPage(recorder, 403, "/redirect", "Debug error")
|
||||
errorPage.WriteErrorPage(recorder, ErrorPageOpts{
|
||||
Status: 403,
|
||||
RedirectURL: "/redirect",
|
||||
AppError: "Debug error",
|
||||
})
|
||||
|
||||
body, err := ioutil.ReadAll(recorder.Result().Body)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
@@ -89,6 +130,9 @@ var _ = Describe("Error Page Writer", func() {
|
||||
Context("ProxyErrorHandler", func() {
|
||||
It("Writes a bad gateway error the response writer", func() {
|
||||
req := httptest.NewRequest("", "/bad-gateway", nil)
|
||||
req = middlewareapi.AddRequestScope(req, &middlewareapi.RequestScope{
|
||||
RequestID: testRequestID,
|
||||
})
|
||||
recorder := httptest.NewRecorder()
|
||||
errorPage.ProxyErrorHandler(recorder, req, errors.New("some upstream error"))
|
||||
|
||||
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
// It can also be used to write errors for the http.ReverseProxy used in the
|
||||
// upstream package.
|
||||
type Writer interface {
|
||||
WriteSignInPage(rw http.ResponseWriter, redirectURL string)
|
||||
WriteErrorPage(rw http.ResponseWriter, status int, redirectURL string, appError string, messages ...interface{})
|
||||
WriteSignInPage(rw http.ResponseWriter, req *http.Request, redirectURL string)
|
||||
WriteErrorPage(rw http.ResponseWriter, opts ErrorPageOpts)
|
||||
ProxyErrorHandler(rw http.ResponseWriter, req *http.Request, proxyErr error)
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
const testRequestID = "11111111-2222-4333-8444-555555555555"
|
||||
|
||||
func TestOptionsSuite(t *testing.T) {
|
||||
logger.SetOutput(GinkgoWriter)
|
||||
logger.SetErrOutput(GinkgoWriter)
|
||||
|
||||
@@ -2,6 +2,7 @@ package pagewriter
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -14,6 +15,7 @@ var _ = Describe("Writer", func() {
|
||||
Context("NewWriter", func() {
|
||||
var writer Writer
|
||||
var opts Opts
|
||||
var request *http.Request
|
||||
|
||||
BeforeEach(func() {
|
||||
opts = Opts{
|
||||
@@ -26,6 +28,8 @@ var _ = Describe("Writer", func() {
|
||||
ProviderName: "<ProviderName>",
|
||||
SignInMessage: "<SignInMessage>",
|
||||
}
|
||||
|
||||
request = httptest.NewRequest("", "http://127.0.0.1/", nil)
|
||||
})
|
||||
|
||||
Context("With no custom templates", func() {
|
||||
@@ -37,7 +41,11 @@ var _ = Describe("Writer", func() {
|
||||
|
||||
It("Writes the default error template", func() {
|
||||
recorder := httptest.NewRecorder()
|
||||
writer.WriteErrorPage(recorder, 500, "/redirect", "Some debug error")
|
||||
writer.WriteErrorPage(recorder, ErrorPageOpts{
|
||||
Status: 500,
|
||||
RedirectURL: "/redirect",
|
||||
AppError: "Some debug error",
|
||||
})
|
||||
|
||||
body, err := ioutil.ReadAll(recorder.Result().Body)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
@@ -46,7 +54,7 @@ var _ = Describe("Writer", func() {
|
||||
|
||||
It("Writes the default sign in template", func() {
|
||||
recorder := httptest.NewRecorder()
|
||||
writer.WriteSignInPage(recorder, "/redirect")
|
||||
writer.WriteSignInPage(recorder, request, "/redirect")
|
||||
|
||||
body, err := ioutil.ReadAll(recorder.Result().Body)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
@@ -80,7 +88,11 @@ var _ = Describe("Writer", func() {
|
||||
|
||||
It("Writes the custom error template", func() {
|
||||
recorder := httptest.NewRecorder()
|
||||
writer.WriteErrorPage(recorder, 500, "/redirect", "Some debug error")
|
||||
writer.WriteErrorPage(recorder, ErrorPageOpts{
|
||||
Status: 500,
|
||||
RedirectURL: "/redirect",
|
||||
AppError: "Some debug error",
|
||||
})
|
||||
|
||||
body, err := ioutil.ReadAll(recorder.Result().Body)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
@@ -89,7 +101,7 @@ var _ = Describe("Writer", func() {
|
||||
|
||||
It("Writes the custom sign in template", func() {
|
||||
recorder := httptest.NewRecorder()
|
||||
writer.WriteSignInPage(recorder, "/redirect")
|
||||
writer.WriteSignInPage(recorder, request, "/redirect")
|
||||
|
||||
body, err := ioutil.ReadAll(recorder.Result().Body)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"html/template"
|
||||
"net/http"
|
||||
|
||||
middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
|
||||
)
|
||||
|
||||
@@ -53,7 +54,7 @@ type signInPageWriter struct {
|
||||
|
||||
// WriteSignInPage writes the sign-in page to the given response writer.
|
||||
// It uses the redirectURL to be able to set the final destination for the user post login.
|
||||
func (s *signInPageWriter) WriteSignInPage(rw http.ResponseWriter, redirectURL string) {
|
||||
func (s *signInPageWriter) WriteSignInPage(rw http.ResponseWriter, req *http.Request, redirectURL string) {
|
||||
// We allow unescaped template.HTML since it is user configured options
|
||||
/* #nosec G203 */
|
||||
t := struct {
|
||||
@@ -79,7 +80,13 @@ func (s *signInPageWriter) WriteSignInPage(rw http.ResponseWriter, redirectURL s
|
||||
err := s.template.Execute(rw, t)
|
||||
if err != nil {
|
||||
logger.Printf("Error rendering sign-in template: %v", err)
|
||||
s.errorPageWriter.WriteErrorPage(rw, http.StatusInternalServerError, redirectURL, err.Error())
|
||||
scope := middlewareapi.GetRequestScope(req)
|
||||
s.errorPageWriter.WriteErrorPage(rw, ErrorPageOpts{
|
||||
Status: http.StatusInternalServerError,
|
||||
RedirectURL: redirectURL,
|
||||
RequestID: scope.RequestID,
|
||||
AppError: err.Error(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,11 +5,13 @@ import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/ginkgo/extensions/table"
|
||||
. "github.com/onsi/gomega"
|
||||
@@ -18,10 +20,11 @@ import (
|
||||
var _ = Describe("SignIn Page", func() {
|
||||
|
||||
Context("SignIn Page Writer", func() {
|
||||
var request *http.Request
|
||||
var signInPage *signInPageWriter
|
||||
|
||||
BeforeEach(func() {
|
||||
errorTmpl, err := template.New("").Parse("{{.Title}}")
|
||||
errorTmpl, err := template.New("").Parse("{{.Title}} | {{.RequestID}}")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
errorPage := &errorPageWriter{
|
||||
template: errorTmpl,
|
||||
@@ -41,12 +44,17 @@ var _ = Describe("SignIn Page", func() {
|
||||
displayLoginForm: true,
|
||||
logoData: "Logo Data",
|
||||
}
|
||||
|
||||
request = httptest.NewRequest("", "http://127.0.0.1/", nil)
|
||||
request = middlewareapi.AddRequestScope(request, &middlewareapi.RequestScope{
|
||||
RequestID: testRequestID,
|
||||
})
|
||||
})
|
||||
|
||||
Context("WriteSignInPage", func() {
|
||||
It("Writes the template to the response writer", func() {
|
||||
recorder := httptest.NewRecorder()
|
||||
signInPage.WriteSignInPage(recorder, "/redirect")
|
||||
signInPage.WriteSignInPage(recorder, request, "/redirect")
|
||||
|
||||
body, err := ioutil.ReadAll(recorder.Result().Body)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
@@ -60,11 +68,11 @@ var _ = Describe("SignIn Page", func() {
|
||||
signInPage.template = tmpl
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
signInPage.WriteSignInPage(recorder, "/redirect")
|
||||
signInPage.WriteSignInPage(recorder, request, "/redirect")
|
||||
|
||||
body, err := ioutil.ReadAll(recorder.Result().Body)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(string(body)).To(Equal("Internal Server Error"))
|
||||
Expect(string(body)).To(Equal(fmt.Sprintf("Internal Server Error | %s", testRequestID)))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -51,6 +51,7 @@ var _ = Describe("Templates", func() {
|
||||
StatusCode int
|
||||
Title string
|
||||
Message string
|
||||
RequestID string
|
||||
|
||||
// For custom templates
|
||||
TestString string
|
||||
@@ -67,6 +68,7 @@ var _ = Describe("Templates", func() {
|
||||
StatusCode: 404,
|
||||
Title: "<title>",
|
||||
Message: "<message>",
|
||||
RequestID: "<request-id>",
|
||||
|
||||
TestString: "Testing",
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
|
||||
requestutil "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests/util"
|
||||
)
|
||||
|
||||
@@ -25,9 +26,9 @@ const (
|
||||
// DefaultStandardLoggingFormat defines the default standard log format
|
||||
DefaultStandardLoggingFormat = "[{{.Timestamp}}] [{{.File}}] {{.Message}}"
|
||||
// DefaultAuthLoggingFormat defines the default auth log format
|
||||
DefaultAuthLoggingFormat = "{{.Client}} - {{.Username}} [{{.Timestamp}}] [{{.Status}}] {{.Message}}"
|
||||
DefaultAuthLoggingFormat = "{{.Client}} - {{.RequestID}} - {{.Username}} [{{.Timestamp}}] [{{.Status}}] {{.Message}}"
|
||||
// DefaultRequestLoggingFormat defines the default request log format
|
||||
DefaultRequestLoggingFormat = "{{.Client}} - {{.Username}} [{{.Timestamp}}] {{.Host}} {{.RequestMethod}} {{.Upstream}} {{.RequestURI}} {{.Protocol}} {{.UserAgent}} {{.StatusCode}} {{.ResponseSize}} {{.RequestDuration}}"
|
||||
DefaultRequestLoggingFormat = "{{.Client}} - {{.RequestID}} - {{.Username}} [{{.Timestamp}}] {{.Host}} {{.RequestMethod}} {{.Upstream}} {{.RequestURI}} {{.Protocol}} {{.UserAgent}} {{.StatusCode}} {{.ResponseSize}} {{.RequestDuration}}"
|
||||
|
||||
// AuthSuccess indicates that an auth attempt has succeeded explicitly
|
||||
AuthSuccess AuthStatus = "AuthSuccess"
|
||||
@@ -63,6 +64,7 @@ type authLogMessageData struct {
|
||||
Client,
|
||||
Host,
|
||||
Protocol,
|
||||
RequestID,
|
||||
RequestMethod,
|
||||
Timestamp,
|
||||
UserAgent,
|
||||
@@ -75,6 +77,7 @@ type reqLogMessageData struct {
|
||||
Client,
|
||||
Host,
|
||||
Protocol,
|
||||
RequestID,
|
||||
RequestDuration,
|
||||
RequestMethod,
|
||||
RequestURI,
|
||||
@@ -195,10 +198,12 @@ func (l *Logger) PrintAuthf(username string, req *http.Request, status AuthStatu
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
scope := middlewareapi.GetRequestScope(req)
|
||||
err := l.authTemplate.Execute(l.writer, authLogMessageData{
|
||||
Client: client,
|
||||
Host: requestutil.GetRequestHost(req),
|
||||
Protocol: req.Proto,
|
||||
RequestID: scope.RequestID,
|
||||
RequestMethod: req.Method,
|
||||
Timestamp: FormatTimestamp(now),
|
||||
UserAgent: fmt.Sprintf("%q", req.UserAgent()),
|
||||
@@ -249,10 +254,12 @@ func (l *Logger) PrintReq(username, upstream string, req *http.Request, url url.
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
scope := middlewareapi.GetRequestScope(req)
|
||||
err := l.reqTemplate.Execute(l.writer, reqLogMessageData{
|
||||
Client: client,
|
||||
Host: requestutil.GetRequestHost(req),
|
||||
Protocol: req.Proto,
|
||||
RequestID: scope.RequestID,
|
||||
RequestDuration: fmt.Sprintf("%0.3f", duration),
|
||||
RequestMethod: req.Method,
|
||||
RequestURI: fmt.Sprintf("%q", url.RequestURI()),
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
const RequestLoggingFormatWithoutTime = "{{.Client}} - {{.Username}} [TIMELESS] {{.Host}} {{.RequestMethod}} {{.Upstream}} {{.RequestURI}} {{.Protocol}} {{.UserAgent}} {{.StatusCode}} {{.ResponseSize}} {{.RequestDuration}}"
|
||||
const RequestLoggingFormatWithoutTime = "{{.Client}} - {{.RequestID}} - {{.Username}} [TIMELESS] {{.Host}} {{.RequestMethod}} {{.Upstream}} {{.RequestURI}} {{.Protocol}} {{.UserAgent}} {{.StatusCode}} {{.ResponseSize}} {{.RequestDuration}}"
|
||||
|
||||
var _ = Describe("Request logger suite", func() {
|
||||
type requestLoggerTableInput struct {
|
||||
@@ -37,7 +37,10 @@ var _ = Describe("Request logger suite", func() {
|
||||
req.RemoteAddr = "127.0.0.1"
|
||||
req.Host = "test-server"
|
||||
|
||||
scope := &middlewareapi.RequestScope{Session: in.Session}
|
||||
scope := &middlewareapi.RequestScope{
|
||||
RequestID: "11111111-2222-4333-8444-555555555555",
|
||||
Session: in.Session,
|
||||
}
|
||||
req = middlewareapi.AddRequestScope(req, scope)
|
||||
|
||||
handler := NewRequestLogger()(testUpstreamHandler(in.Upstream))
|
||||
@@ -47,7 +50,7 @@ var _ = Describe("Request logger suite", func() {
|
||||
},
|
||||
Entry("standard request", &requestLoggerTableInput{
|
||||
Format: RequestLoggingFormatWithoutTime,
|
||||
ExpectedLogMessage: "127.0.0.1 - standard.user [TIMELESS] test-server GET standard \"/foo/bar\" HTTP/1.1 \"\" 200 4 0.000\n",
|
||||
ExpectedLogMessage: "127.0.0.1 - 11111111-2222-4333-8444-555555555555 - standard.user [TIMELESS] test-server GET standard \"/foo/bar\" HTTP/1.1 \"\" 200 4 0.000\n",
|
||||
Path: "/foo/bar",
|
||||
ExcludePaths: []string{},
|
||||
Upstream: "standard",
|
||||
@@ -55,7 +58,7 @@ var _ = Describe("Request logger suite", func() {
|
||||
}),
|
||||
Entry("with unrelated path excluded", &requestLoggerTableInput{
|
||||
Format: RequestLoggingFormatWithoutTime,
|
||||
ExpectedLogMessage: "127.0.0.1 - unrelated.exclusion [TIMELESS] test-server GET unrelated \"/foo/bar\" HTTP/1.1 \"\" 200 4 0.000\n",
|
||||
ExpectedLogMessage: "127.0.0.1 - 11111111-2222-4333-8444-555555555555 - unrelated.exclusion [TIMELESS] test-server GET unrelated \"/foo/bar\" HTTP/1.1 \"\" 200 4 0.000\n",
|
||||
Path: "/foo/bar",
|
||||
ExcludePaths: []string{"/ping"},
|
||||
Upstream: "unrelated",
|
||||
@@ -69,7 +72,7 @@ var _ = Describe("Request logger suite", func() {
|
||||
}),
|
||||
Entry("ping path", &requestLoggerTableInput{
|
||||
Format: RequestLoggingFormatWithoutTime,
|
||||
ExpectedLogMessage: "127.0.0.1 - mr.ping [TIMELESS] test-server GET - \"/ping\" HTTP/1.1 \"\" 200 4 0.000\n",
|
||||
ExpectedLogMessage: "127.0.0.1 - 11111111-2222-4333-8444-555555555555 - mr.ping [TIMELESS] test-server GET - \"/ping\" HTTP/1.1 \"\" 200 4 0.000\n",
|
||||
Path: "/ping",
|
||||
ExcludePaths: []string{},
|
||||
Upstream: "",
|
||||
|
||||
@@ -3,18 +3,31 @@ package middleware
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/justinas/alice"
|
||||
middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
|
||||
)
|
||||
|
||||
func NewScope(reverseProxy bool) alice.Constructor {
|
||||
func NewScope(reverseProxy bool, idHeader string) alice.Constructor {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
scope := &middlewareapi.RequestScope{
|
||||
ReverseProxy: reverseProxy,
|
||||
RequestID: genRequestID(req, idHeader),
|
||||
}
|
||||
req = middlewareapi.AddRequestScope(req, scope)
|
||||
next.ServeHTTP(rw, req)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// genRequestID sets a request-wide ID for use in logging or error pages.
|
||||
// If a RequestID header is set, it uses that. Otherwise, it generates a random
|
||||
// UUID for the lifespan of the request.
|
||||
func genRequestID(req *http.Request, idHeader string) string {
|
||||
rid := req.Header.Get(idHeader)
|
||||
if rid != "" {
|
||||
return rid
|
||||
}
|
||||
return uuid.New().String()
|
||||
}
|
||||
|
||||
@@ -4,11 +4,19 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
||||
"github.com/google/uuid"
|
||||
middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
const (
|
||||
testRequestHeader = "X-Request-Id"
|
||||
testRequestID = "11111111-2222-4333-8444-555555555555"
|
||||
// mockRand io.Reader below counts bytes from 0-255 in order
|
||||
testRandomUUID = "00010203-0405-4607-8809-0a0b0c0d0e0f"
|
||||
)
|
||||
|
||||
var _ = Describe("Scope Suite", func() {
|
||||
Context("NewScope", func() {
|
||||
var request, nextRequest *http.Request
|
||||
@@ -24,10 +32,11 @@ var _ = Describe("Scope Suite", func() {
|
||||
|
||||
Context("ReverseProxy is false", func() {
|
||||
BeforeEach(func() {
|
||||
handler := NewScope(false)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
nextRequest = r
|
||||
w.WriteHeader(200)
|
||||
}))
|
||||
handler := NewScope(false, testRequestHeader)(
|
||||
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
nextRequest = r
|
||||
w.WriteHeader(200)
|
||||
}))
|
||||
handler.ServeHTTP(rw, request)
|
||||
})
|
||||
|
||||
@@ -52,10 +61,11 @@ var _ = Describe("Scope Suite", func() {
|
||||
|
||||
Context("ReverseProxy is true", func() {
|
||||
BeforeEach(func() {
|
||||
handler := NewScope(true)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
nextRequest = r
|
||||
w.WriteHeader(200)
|
||||
}))
|
||||
handler := NewScope(true, testRequestHeader)(
|
||||
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
nextRequest = r
|
||||
w.WriteHeader(200)
|
||||
}))
|
||||
handler.ServeHTTP(rw, request)
|
||||
})
|
||||
|
||||
@@ -65,5 +75,53 @@ var _ = Describe("Scope Suite", func() {
|
||||
Expect(scope.ReverseProxy).To(BeTrue())
|
||||
})
|
||||
})
|
||||
|
||||
Context("Request ID header is present", func() {
|
||||
BeforeEach(func() {
|
||||
request.Header.Add(testRequestHeader, testRequestID)
|
||||
handler := NewScope(false, testRequestHeader)(
|
||||
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
nextRequest = r
|
||||
w.WriteHeader(200)
|
||||
}))
|
||||
handler.ServeHTTP(rw, request)
|
||||
})
|
||||
|
||||
It("sets the RequestID using the request", func() {
|
||||
scope := middlewareapi.GetRequestScope(nextRequest)
|
||||
Expect(scope.RequestID).To(Equal(testRequestID))
|
||||
})
|
||||
})
|
||||
|
||||
Context("Request ID header is missing", func() {
|
||||
BeforeEach(func() {
|
||||
uuid.SetRand(mockRand{})
|
||||
|
||||
handler := NewScope(true, testRequestHeader)(
|
||||
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
nextRequest = r
|
||||
w.WriteHeader(200)
|
||||
}))
|
||||
handler.ServeHTTP(rw, request)
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
uuid.SetRand(nil)
|
||||
})
|
||||
|
||||
It("sets the RequestID using a random UUID", func() {
|
||||
scope := middlewareapi.GetRequestScope(nextRequest)
|
||||
Expect(scope.RequestID).To(Equal(testRandomUUID))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
type mockRand struct{}
|
||||
|
||||
func (mockRand) Read(p []byte) (int, error) {
|
||||
for i := range p {
|
||||
p[i] = byte(i % 256)
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
@@ -401,6 +401,7 @@ var _ = Describe("Stored Session Suite", func() {
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("", "/", nil)
|
||||
req = middlewareapi.AddRequestScope(req, &middlewareapi.RequestScope{})
|
||||
refreshed, err := s.refreshSessionWithProvider(nil, req, in.session)
|
||||
if in.expectedErr != nil {
|
||||
Expect(err).To(MatchError(in.expectedErr))
|
||||
|
||||
@@ -6,10 +6,16 @@ import (
|
||||
middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
|
||||
)
|
||||
|
||||
const (
|
||||
XForwardedProto = "X-Forwarded-Proto"
|
||||
XForwardedHost = "X-Forwarded-Host"
|
||||
XForwardedURI = "X-Forwarded-Uri"
|
||||
)
|
||||
|
||||
// GetRequestProto returns the request scheme or X-Forwarded-Proto if present
|
||||
// and the request is proxied.
|
||||
func GetRequestProto(req *http.Request) string {
|
||||
proto := req.Header.Get("X-Forwarded-Proto")
|
||||
proto := req.Header.Get(XForwardedProto)
|
||||
if !IsProxied(req) || proto == "" {
|
||||
proto = req.URL.Scheme
|
||||
}
|
||||
@@ -19,7 +25,7 @@ func GetRequestProto(req *http.Request) string {
|
||||
// GetRequestHost returns the request host header or X-Forwarded-Host if
|
||||
// present and the request is proxied.
|
||||
func GetRequestHost(req *http.Request) string {
|
||||
host := req.Header.Get("X-Forwarded-Host")
|
||||
host := req.Header.Get(XForwardedHost)
|
||||
if !IsProxied(req) || host == "" {
|
||||
host = req.Host
|
||||
}
|
||||
@@ -29,7 +35,7 @@ func GetRequestHost(req *http.Request) string {
|
||||
// GetRequestURI return the request URI or X-Forwarded-Uri if present and the
|
||||
// request is proxied.
|
||||
func GetRequestURI(req *http.Request) string {
|
||||
uri := req.Header.Get("X-Forwarded-Uri")
|
||||
uri := req.Header.Get(XForwardedURI)
|
||||
if !IsProxied(req) || uri == "" {
|
||||
// Use RequestURI to preserve ?query
|
||||
uri = req.URL.RequestURI()
|
||||
|
||||
@@ -390,7 +390,7 @@ var _ = Describe("HTTP Upstream Suite", func() {
|
||||
|
||||
handler := newHTTPUpstreamProxy(upstream, u, nil, nil)
|
||||
|
||||
proxyServer = httptest.NewServer(middleware.NewScope(false)(handler))
|
||||
proxyServer = httptest.NewServer(middleware.NewScope(false, "X-Request-Id")(handler))
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
|
||||
Reference in New Issue
Block a user