From 950c65dd538c467a3a70134557c90cf4ca7a9234 Mon Sep 17 00:00:00 2001 From: Dimitrij Drus Date: Thu, 16 Dec 2021 17:38:37 +0100 Subject: [PATCH 01/17] made verification of claims return Unauthorized errors instead of InternalServer errors on expectation failures --- credentials/verifier_default.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/credentials/verifier_default.go b/credentials/verifier_default.go index b78c6c0f2c..adc6ff6341 100644 --- a/credentials/verifier_default.go +++ b/credentials/verifier_default.go @@ -5,6 +5,7 @@ import ( "crypto/ecdsa" "crypto/rsa" "fmt" + "strings" "github.com/golang-jwt/jwt/v4" "github.com/pkg/errors" @@ -100,13 +101,14 @@ func (v *VerifierDefault) Verify( parsedClaims := jwtx.ParseMapStringInterfaceClaims(claims) for _, audience := range r.Audiences { if !stringslice.Has(parsedClaims.Audience, audience) { - return nil, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("Token audience %v is not intended for target audience %s.", parsedClaims.Audience, audience)) + return nil, herodot.ErrUnauthorized.WithReasonf("Token audience %v is not intended for target audience %s.", parsedClaims.Audience, audience) } } if len(r.Issuers) > 0 { if !stringslice.Has(r.Issuers, parsedClaims.Issuer) { - return nil, errors.WithStack(herodot.ErrInternalServerError.WithReason("Token issuer does not match any trusted issuer.")) + return nil, herodot.ErrUnauthorized.WithReasonf("Token issuer does not match any trusted issuer %s.", parsedClaims.Issuer). + WithDetail("received issuers", strings.Join(r.Issuers, ", ")) } } @@ -117,7 +119,7 @@ func (v *VerifierDefault) Verify( if r.ScopeStrategy != nil { for _, sc := range r.Scope { if !r.ScopeStrategy(s, sc) { - return nil, errors.WithStack(herodot.ErrInternalServerError.WithReasonf(`JSON Web Token is missing required scope "%s".`, sc)) + return nil, herodot.ErrUnauthorized.WithReasonf(`JSON Web Token is missing required scope "%s".`, sc) } } } else { From 5cddcfe3e460b2323ae5d50e519f430b8d7c4c1d Mon Sep 17 00:00:00 2001 From: Dimitrij Drus Date: Thu, 16 Dec 2021 17:42:03 +0100 Subject: [PATCH 02/17] added simple support for traefik --- api/decision.go | 34 +++++++++++++++++++++++++++++-- pipeline/errors/error_redirect.go | 23 ++++++++++++++++++--- 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/api/decision.go b/api/decision.go index 2325ec91fe..7c6f836800 100644 --- a/api/decision.go +++ b/api/decision.go @@ -21,7 +21,9 @@ package api import ( + "fmt" "net/http" + "net/url" "strings" "github.com/ory/oathkeeper/pipeline/authn" @@ -92,27 +94,55 @@ func (h *DecisionHandler) decisions(w http.ResponseWriter, r *http.Request) { "http_user_agent": r.UserAgent(), } + httpMethod := r.Header.Get("X-Forwarded-Method") + proto := r.Header.Get("X-Forwarded-Proto") + host := r.Header.Get("X-Forwarded-Host") + requestUri := r.Header.Get("X-Forwarded-Uri") + + if httpMethod == "" { + httpMethod = r.Method + } + + var uri *url.URL + var err error + if proto != "" && host != "" && requestUri != "" { + uri, err = url.Parse(fmt.Sprintf("%s://%s%s", proto, host, requestUri)) + if err != nil { + h.r.Logger().WithError(err). + WithFields(fields). + WithField("granted", false). + Warn("Access request denied") + h.r.Logger().Info("Failed to parse the received url. Handling error") + h.r.ProxyRequestHandler().HandleError(w, r, nil, err) + return + } + } else { + uri = r.URL + } + if sess, ok := r.Context().Value(proxy.ContextKeySession).(*authn.AuthenticationSession); ok { fields["subject"] = sess.Subject } - rl, err := h.r.RuleMatcher().Match(r.Context(), r.Method, r.URL) + rl, err := h.r.RuleMatcher().Match(r.Context(), httpMethod, uri) if err != nil { h.r.Logger().WithError(err). WithFields(fields). WithField("granted", false). Warn("Access request denied") + h.r.Logger().Info("Rule matcher failed. Handling error") h.r.ProxyRequestHandler().HandleError(w, r, rl, err) return } + h.r.Logger().Info("Handling Proxy Request") s, err := h.r.ProxyRequestHandler().HandleRequest(r, rl) if err != nil { h.r.Logger().WithError(err). WithFields(fields). WithField("granted", false). Info("Access request denied") - + h.r.Logger().Info("Proxy request handler failed. Handling error") h.r.ProxyRequestHandler().HandleError(w, r, rl, err) return } diff --git a/pipeline/errors/error_redirect.go b/pipeline/errors/error_redirect.go index 4d90498d0d..58a8f99115 100644 --- a/pipeline/errors/error_redirect.go +++ b/pipeline/errors/error_redirect.go @@ -2,6 +2,7 @@ package errors import ( "encoding/json" + "fmt" "net/http" "net/url" @@ -40,7 +41,22 @@ func (a *ErrorRedirect) Handle(w http.ResponseWriter, r *http.Request, config js return err } - http.Redirect(w, r, a.RedirectURL(r, c), c.Code) + proto := r.Header.Get("X-Forwarded-Proto") + host := r.Header.Get("X-Forwarded-Host") + requestUri := r.Header.Get("X-Forwarded-Uri") + + fmt.Printf("Headers: %s, %s, %s\n", proto, host, requestUri) + + var uri *url.URL + if proto != "" && host != "" && requestUri != "" { + if uri, err = url.Parse(fmt.Sprintf("%s://%s%s", proto, host, requestUri)); err != nil { + return err + } + } else { + uri = r.URL + } + + http.Redirect(w, r, a.RedirectURL(uri, c), c.Code) return nil } @@ -69,7 +85,7 @@ func (a *ErrorRedirect) GetID() string { return "redirect" } -func (a *ErrorRedirect) RedirectURL(r *http.Request, c *ErrorRedirectConfig) string { +func (a *ErrorRedirect) RedirectURL(uri *url.URL, c *ErrorRedirectConfig) string { if c.ReturnToQueryParam == "" { return c.To } @@ -78,8 +94,9 @@ func (a *ErrorRedirect) RedirectURL(r *http.Request, c *ErrorRedirectConfig) str if err != nil { return c.To } + q := u.Query() - q.Set(c.ReturnToQueryParam, r.URL.String()) + q.Set(c.ReturnToQueryParam, uri.String()) u.RawQuery = q.Encode() return u.String() } From fd5ee3c95f0017cc1513835fc526836bd0629ae2 Mon Sep 17 00:00:00 2001 From: Dimitrij Drus Date: Fri, 17 Dec 2021 14:34:18 +0100 Subject: [PATCH 03/17] made docker build working --- Dockerfile-dc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile-dc b/Dockerfile-dc index f7a02d4742..27cf4f10c9 100644 --- a/Dockerfile-dc +++ b/Dockerfile-dc @@ -5,10 +5,11 @@ RUN addgroup -S ory; \ RUN apk add -U --no-cache ca-certificates +RUN go get -u github.com/gobuffalo/packr/v2/packr2 ADD . /app WORKDIR /app ENV GO111MODULE on -RUN go get -u github.com/gobuffalo/packr/v2/packr2 +RUN go mod download RUN packr2 RUN go mod download && go mod tidy RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build From a7c706f3ad04c1abb1afc80d05936eb8e78d0aee Mon Sep 17 00:00:00 2001 From: Dimitrij Drus Date: Tue, 1 Feb 2022 07:04:40 +0100 Subject: [PATCH 04/17] fixes to make the old tests successful --- api/decision.go | 59 +++++++++++++++---------------- credentials/verifier_default.go | 6 ++-- pipeline/errors/error_redirect.go | 27 ++++++++------ 3 files changed, 48 insertions(+), 44 deletions(-) diff --git a/api/decision.go b/api/decision.go index 7c6f836800..73abab0bb6 100644 --- a/api/decision.go +++ b/api/decision.go @@ -87,62 +87,59 @@ func (h *DecisionHandler) ServeHTTP(w http.ResponseWriter, r *http.Request, next // 404: genericError // 500: genericError func (h *DecisionHandler) decisions(w http.ResponseWriter, r *http.Request) { - fields := map[string]interface{}{ - "http_method": r.Method, - "http_url": r.URL.String(), - "http_host": r.Host, - "http_user_agent": r.UserAgent(), - } + var method, scheme, host, requestUri string - httpMethod := r.Header.Get("X-Forwarded-Method") - proto := r.Header.Get("X-Forwarded-Proto") - host := r.Header.Get("X-Forwarded-Host") - requestUri := r.Header.Get("X-Forwarded-Uri") + if method = r.Header.Get("X-Forwarded-Method"); method == "" { + method = r.Method + } + if scheme = r.Header.Get("X-Forwarded-Proto"); scheme == "" { + scheme = r.URL.Scheme + } + if host = r.Header.Get("X-Forwarded-Host"); host == "" { + host = r.URL.Host + } + if requestUri = r.Header.Get("X-Forwarded-Uri"); requestUri == "" { + requestUri = r.URL.RequestURI() + } - if httpMethod == "" { - httpMethod = r.Method + fields := map[string]interface{}{ + "http_method": method, + "http_scheme": scheme, + "http_host": host, + "http_request_uri": requestUri, + "http_user_agent": r.UserAgent(), } - var uri *url.URL - var err error - if proto != "" && host != "" && requestUri != "" { - uri, err = url.Parse(fmt.Sprintf("%s://%s%s", proto, host, requestUri)) - if err != nil { - h.r.Logger().WithError(err). - WithFields(fields). - WithField("granted", false). - Warn("Access request denied") - h.r.Logger().Info("Failed to parse the received url. Handling error") - h.r.ProxyRequestHandler().HandleError(w, r, nil, err) - return - } - } else { - uri = r.URL + uri, err := url.Parse(fmt.Sprintf("%s://%s%s", scheme, host, requestUri)) + if err != nil { + h.r.Logger().WithError(err). + WithFields(fields). + WithField("granted", false). + Warn("Access request denied") + h.r.ProxyRequestHandler().HandleError(w, r, nil, err) + return } if sess, ok := r.Context().Value(proxy.ContextKeySession).(*authn.AuthenticationSession); ok { fields["subject"] = sess.Subject } - rl, err := h.r.RuleMatcher().Match(r.Context(), httpMethod, uri) + rl, err := h.r.RuleMatcher().Match(r.Context(), method, uri) if err != nil { h.r.Logger().WithError(err). WithFields(fields). WithField("granted", false). Warn("Access request denied") - h.r.Logger().Info("Rule matcher failed. Handling error") h.r.ProxyRequestHandler().HandleError(w, r, rl, err) return } - h.r.Logger().Info("Handling Proxy Request") s, err := h.r.ProxyRequestHandler().HandleRequest(r, rl) if err != nil { h.r.Logger().WithError(err). WithFields(fields). WithField("granted", false). Info("Access request denied") - h.r.Logger().Info("Proxy request handler failed. Handling error") h.r.ProxyRequestHandler().HandleError(w, r, rl, err) return } diff --git a/credentials/verifier_default.go b/credentials/verifier_default.go index adc6ff6341..e1ed43d07d 100644 --- a/credentials/verifier_default.go +++ b/credentials/verifier_default.go @@ -43,7 +43,7 @@ func (v *VerifierDefault) Verify( kid, ok := token.Header["kid"].(string) if !ok || kid == "" { - return nil, errors.WithStack(herodot.ErrInternalServerError.WithReason("The JSON Web Token must contain a kid header value but did not.")) + return nil, errors.WithStack(herodot.ErrBadRequest.WithReason("The JSON Web Token must contain a kid header value but did not.")) } key, err := v.r.CredentialsFetcher().ResolveKey(ctx, r.KeyURLs, kid, "sig") @@ -75,10 +75,10 @@ func (v *VerifierDefault) Verify( return k, nil } default: - return nil, errors.WithStack(herodot.ErrInternalServerError.WithReasonf(`This request object uses unsupported signing algorithm "%s".`, token.Header["alg"])) + return nil, errors.WithStack(herodot.ErrBadRequest.WithReasonf(`This request object uses unsupported signing algorithm "%s".`, token.Header["alg"])) } - return nil, errors.WithStack(herodot.ErrInternalServerError.WithReasonf(`The signing key algorithm does not match the algorithm from the token header.`)) + return nil, errors.WithStack(herodot.ErrBadRequest.WithReasonf(`The signing key algorithm does not match the algorithm from the token header.`)) }) if err != nil { if e, ok := errors.Cause(err).(*jwt.ValidationError); ok { diff --git a/pipeline/errors/error_redirect.go b/pipeline/errors/error_redirect.go index 58a8f99115..3083829502 100644 --- a/pipeline/errors/error_redirect.go +++ b/pipeline/errors/error_redirect.go @@ -41,19 +41,26 @@ func (a *ErrorRedirect) Handle(w http.ResponseWriter, r *http.Request, config js return err } - proto := r.Header.Get("X-Forwarded-Proto") - host := r.Header.Get("X-Forwarded-Host") - requestUri := r.Header.Get("X-Forwarded-Uri") - - fmt.Printf("Headers: %s, %s, %s\n", proto, host, requestUri) + var scheme, host, requestUri string + if scheme = r.Header.Get("X-Forwarded-Proto"); scheme == "" { + scheme = r.URL.Scheme + } + if host = r.Header.Get("X-Forwarded-Host"); host == "" { + host = r.URL.Host + } + if requestUri = r.Header.Get("X-Forwarded-Uri"); requestUri == "" { + requestUri = r.URL.RequestURI() + } var uri *url.URL - if proto != "" && host != "" && requestUri != "" { - if uri, err = url.Parse(fmt.Sprintf("%s://%s%s", proto, host, requestUri)); err != nil { - return err - } + if scheme == "" || host == "" { + // FIXME: I don't think this is applicable for real requests. It is however used by tests. + uri, err = url.Parse(fmt.Sprintf("%s", requestUri)) } else { - uri = r.URL + uri, err = url.Parse(fmt.Sprintf("%s://%s%s", scheme, host, requestUri)) + } + if err != nil { + return err } http.Redirect(w, r, a.RedirectURL(uri, c), c.Code) From e38d08c14a214929726d65b7fea6955cc9a206a0 Mon Sep 17 00:00:00 2001 From: Dimitrij Drus Date: Tue, 1 Feb 2022 07:11:12 +0100 Subject: [PATCH 05/17] Changes reverted as these correspond to those introduced to master in the last commits --- Dockerfile-dc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Dockerfile-dc b/Dockerfile-dc index 27cf4f10c9..f7a02d4742 100644 --- a/Dockerfile-dc +++ b/Dockerfile-dc @@ -5,11 +5,10 @@ RUN addgroup -S ory; \ RUN apk add -U --no-cache ca-certificates -RUN go get -u github.com/gobuffalo/packr/v2/packr2 ADD . /app WORKDIR /app ENV GO111MODULE on -RUN go mod download +RUN go get -u github.com/gobuffalo/packr/v2/packr2 RUN packr2 RUN go mod download && go mod tidy RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build From eed6ff07503a5730f23a2cf57cc38f93cdd2ee17 Mon Sep 17 00:00:00 2001 From: Dimitrij Drus Date: Tue, 1 Feb 2022 07:49:05 +0100 Subject: [PATCH 06/17] moved trafik specific handling to from decisions to ServeHttp method --- api/decision.go | 70 ++++++++++++++++++++++--------------------------- 1 file changed, 32 insertions(+), 38 deletions(-) diff --git a/api/decision.go b/api/decision.go index 73abab0bb6..4a7dab7f0d 100644 --- a/api/decision.go +++ b/api/decision.go @@ -21,9 +21,7 @@ package api import ( - "fmt" "net/http" - "net/url" "strings" "github.com/ory/oathkeeper/pipeline/authn" @@ -35,6 +33,11 @@ import ( const ( DecisionPath = "/decisions" + + xForwardedMethod = "X-Forwarded-Method" + xForwardedProto = "X-Forwarded-Proto" + xForwardedHost = "X-Forwarded-Host" + xForwardedUri = "X-Forwarded-Uri" ) type decisionHandlerRegistry interface { @@ -55,12 +58,29 @@ func NewJudgeHandler(r decisionHandlerRegistry) *DecisionHandler { func (h *DecisionHandler) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { if len(r.URL.Path) >= len(DecisionPath) && r.URL.Path[:len(DecisionPath)] == DecisionPath { - r.URL.Scheme = "http" - r.URL.Host = r.Host - if r.TLS != nil || strings.EqualFold(r.Header.Get("X-Forwarded-Proto"), "https") { - r.URL.Scheme = "https" + var method, scheme, host, path string + + if method = r.Header.Get(xForwardedMethod); method == "" { + method = r.Method + } + if scheme = r.Header.Get(xForwardedProto); scheme == "" { + if r.TLS != nil { + scheme = "https" + } else { + scheme = "http" + } + } + if host = r.Header.Get(xForwardedHost); host == "" { + host = r.Host + } + if path = r.Header.Get(xForwardedUri); path == "" { + path = r.URL.Path[len(DecisionPath):] } - r.URL.Path = r.URL.Path[len(DecisionPath):] + + r.Method = method + r.URL.Scheme = scheme + r.URL.Host = host + r.URL.Path = path h.decisions(w, r) } else { @@ -87,44 +107,18 @@ func (h *DecisionHandler) ServeHTTP(w http.ResponseWriter, r *http.Request, next // 404: genericError // 500: genericError func (h *DecisionHandler) decisions(w http.ResponseWriter, r *http.Request) { - var method, scheme, host, requestUri string - - if method = r.Header.Get("X-Forwarded-Method"); method == "" { - method = r.Method - } - if scheme = r.Header.Get("X-Forwarded-Proto"); scheme == "" { - scheme = r.URL.Scheme - } - if host = r.Header.Get("X-Forwarded-Host"); host == "" { - host = r.URL.Host - } - if requestUri = r.Header.Get("X-Forwarded-Uri"); requestUri == "" { - requestUri = r.URL.RequestURI() - } - fields := map[string]interface{}{ - "http_method": method, - "http_scheme": scheme, - "http_host": host, - "http_request_uri": requestUri, - "http_user_agent": r.UserAgent(), - } - - uri, err := url.Parse(fmt.Sprintf("%s://%s%s", scheme, host, requestUri)) - if err != nil { - h.r.Logger().WithError(err). - WithFields(fields). - WithField("granted", false). - Warn("Access request denied") - h.r.ProxyRequestHandler().HandleError(w, r, nil, err) - return + "http_method": r.Method, + "http_url": r.URL.String(), + "http_host": r.Host, + "http_user_agent": r.UserAgent(), } if sess, ok := r.Context().Value(proxy.ContextKeySession).(*authn.AuthenticationSession); ok { fields["subject"] = sess.Subject } - rl, err := h.r.RuleMatcher().Match(r.Context(), method, uri) + rl, err := h.r.RuleMatcher().Match(r.Context(), r.Method, r.URL) if err != nil { h.r.Logger().WithError(err). WithFields(fields). From 546fedc1b065d22c4fe8310c8a645c66af83d228 Mon Sep 17 00:00:00 2001 From: Dimitrij Drus Date: Thu, 3 Feb 2022 17:19:25 +0100 Subject: [PATCH 07/17] proxy.RequestHandler is now an interface to enable mocking. Tests for the decision api impl updated to cover the header use cases --- api/decision.go | 2 +- api/decision_test.go | 144 ++++++++++++++++++++++++++++++++++++-- driver/registry.go | 2 +- driver/registry_memory.go | 4 +- proxy/proxy.go | 2 +- proxy/request_handler.go | 20 ++++-- 6 files changed, 155 insertions(+), 19 deletions(-) diff --git a/api/decision.go b/api/decision.go index 4a7dab7f0d..2567a46525 100644 --- a/api/decision.go +++ b/api/decision.go @@ -45,7 +45,7 @@ type decisionHandlerRegistry interface { x.RegistryLogger RuleMatcher() rule.Matcher - ProxyRequestHandler() *proxy.RequestHandler + ProxyRequestHandler() proxy.RequestHandler } type DecisionHandler struct { diff --git a/api/decision_test.go b/api/decision_test.go index 06c3b19609..02f1e21b85 100644 --- a/api/decision_test.go +++ b/api/decision_test.go @@ -23,25 +23,30 @@ package api_test import ( "bytes" "context" + "crypto/tls" "fmt" "io/ioutil" "net/http" "net/http/httptest" + "net/url" "strconv" "testing" - "github.com/ory/viper" - - "github.com/urfave/negroni" - - "github.com/ory/oathkeeper/driver/configuration" - "github.com/ory/oathkeeper/internal" - "github.com/julienschmidt/httprouter" + "github.com/ory/herodot" + "github.com/ory/oathkeeper/pipeline/authn" + "github.com/ory/oathkeeper/proxy" + "github.com/ory/x/logrusx" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + "github.com/urfave/negroni" + "github.com/ory/oathkeeper/api" + "github.com/ory/oathkeeper/driver/configuration" + "github.com/ory/oathkeeper/internal" "github.com/ory/oathkeeper/rule" + "github.com/ory/viper" ) func TestDecisionAPI(t *testing.T) { @@ -344,3 +349,128 @@ func TestDecisionAPI(t *testing.T) { }) } } + +type decisionHandlerRegistryMock struct { + mock.Mock +} + +func (m *decisionHandlerRegistryMock) RuleMatcher() rule.Matcher { + return m +} + +func (m *decisionHandlerRegistryMock) ProxyRequestHandler() proxy.RequestHandler { + return m +} + +func (*decisionHandlerRegistryMock) Writer() herodot.Writer { + return nil +} + +func (*decisionHandlerRegistryMock) Logger() *logrusx.Logger { + return logrusx.New("", "") +} + +func (m *decisionHandlerRegistryMock) Match(ctx context.Context, method string, u *url.URL) (*rule.Rule, error) { + args := m.Called(ctx, method, u) + return args.Get(0).(*rule.Rule), args.Error(1) +} + +func (*decisionHandlerRegistryMock) HandleError(w http.ResponseWriter, r *http.Request, rl *rule.Rule, handleErr error) { +} + +func (*decisionHandlerRegistryMock) HandleRequest(r *http.Request, rl *rule.Rule) (session *authn.AuthenticationSession, err error) { + return &authn.AuthenticationSession{}, nil +} + +func (*decisionHandlerRegistryMock) InitializeAuthnSession(r *http.Request, rl *rule.Rule) *authn.AuthenticationSession { + return nil +} + +func TestDecisionAPIHeaderUsage(t *testing.T) { + r := new(decisionHandlerRegistryMock) + h := api.NewJudgeHandler(r) + defaultUrl := &url.URL{Scheme: "http", Host: "ory.sh", Path: "/foo"} + defaultMethod := "GET" + defaultTransform := func(req *http.Request) {} + + for _, tc := range []struct { + name string + expectedMethod string + expectedUrl *url.URL + transform func(req *http.Request) + }{ + { + name: "all arguments are taken from the url and request method", + expectedUrl: defaultUrl, + expectedMethod: defaultMethod, + transform: defaultTransform, + }, + { + name: "all arguments are taken from the url and request method, but scheme from URL TLS settings", + expectedUrl: &url.URL{Scheme: "https", Host: defaultUrl.Host, Path: defaultUrl.Path}, + expectedMethod: defaultMethod, + transform: func(req *http.Request) { + req.TLS = &tls.ConnectionState{} + }, + }, + { + name: "all arguments are taken from the headers", + expectedUrl: &url.URL{Scheme: "https", Host: "test.dev", Path: "/bar"}, + expectedMethod: "POST", + transform: func(req *http.Request) { + req.Header.Add("X-Forwarded-Method", "POST") + req.Header.Add("X-Forwarded-Proto", "https") + req.Header.Add("X-Forwarded-Host", "test.dev") + req.Header.Add("X-Forwarded-Uri", "/bar") + }, + }, + { + name: "only scheme is taken from the headers", + expectedUrl: &url.URL{Scheme: "https", Host: defaultUrl.Host, Path: defaultUrl.Path}, + expectedMethod: defaultMethod, + transform: func(req *http.Request) { + req.Header.Add("X-Forwarded-Proto", "https") + }, + }, + { + name: "only method is taken from the headers", + expectedUrl: defaultUrl, + expectedMethod: "POST", + transform: func(req *http.Request) { + req.Header.Add("X-Forwarded-Method", "POST") + }, + }, + { + name: "only host is taken from the headers", + expectedUrl: &url.URL{Scheme: defaultUrl.Scheme, Host: "test.dev", Path: defaultUrl.Path}, + expectedMethod: defaultMethod, + transform: func(req *http.Request) { + req.Header.Add("X-Forwarded-Host", "test.dev") + }, + }, + { + name: "only path is taken from the headers", + expectedUrl: &url.URL{Scheme: defaultUrl.Scheme, Host: defaultUrl.Host, Path: "/bar"}, + expectedMethod: defaultMethod, + transform: func(req *http.Request) { + req.Header.Add("X-Forwarded-Uri", "/bar") + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + res := httptest.NewRecorder() + reqUrl := *defaultUrl + reqUrl.Path = api.DecisionPath + reqUrl.Path + req := httptest.NewRequest(defaultMethod, reqUrl.String(), nil) + tc.transform(req) + + r.On("Match", mock.Anything, + mock.MatchedBy(func(val string) bool { return val == tc.expectedMethod }), + mock.MatchedBy(func(val *url.URL) bool { return *val == *tc.expectedUrl })). + Return(&rule.Rule{}, nil) + h.ServeHTTP(res, req, nil) + + r.AssertExpectations(t) + }) + } +} diff --git a/driver/registry.go b/driver/registry.go index 14c62daabd..11a8ed7630 100644 --- a/driver/registry.go +++ b/driver/registry.go @@ -30,7 +30,7 @@ type Registry interface { BuildDate() string BuildHash() string - ProxyRequestHandler() *proxy.RequestHandler + ProxyRequestHandler() proxy.RequestHandler HealthEventManager() health.EventManager HealthHandler() *healthx.Handler RuleHandler() *api.RuleHandler diff --git a/driver/registry_memory.go b/driver/registry_memory.go index 69ace4247e..fda492c768 100644 --- a/driver/registry_memory.go +++ b/driver/registry_memory.go @@ -53,7 +53,7 @@ type RegistryMemory struct { apiJudgeHandler *api.DecisionHandler healthxHandler *healthx.Handler - proxyRequestHandler *proxy.RequestHandler + proxyRequestHandler proxy.RequestHandler proxyProxy *proxy.Proxy ruleFetcher rule.Fetcher @@ -89,7 +89,7 @@ func (r *RegistryMemory) WithRuleFetcher(fetcher rule.Fetcher) Registry { return r } -func (r *RegistryMemory) ProxyRequestHandler() *proxy.RequestHandler { +func (r *RegistryMemory) ProxyRequestHandler() proxy.RequestHandler { if r.proxyRequestHandler == nil { r.proxyRequestHandler = proxy.NewRequestHandler(r, r.c) } diff --git a/proxy/proxy.go b/proxy/proxy.go index cca60496fb..2d9c65bdf3 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -39,7 +39,7 @@ type proxyRegistry interface { x.RegistryLogger x.RegistryWriter - ProxyRequestHandler() *RequestHandler + ProxyRequestHandler() RequestHandler RuleMatcher() rule.Matcher } diff --git a/proxy/request_handler.go b/proxy/request_handler.go index 89ff378b86..b9fbd3b20b 100644 --- a/proxy/request_handler.go +++ b/proxy/request_handler.go @@ -51,7 +51,13 @@ type requestHandlerRegistry interface { pe.Registry } -type RequestHandler struct { +type RequestHandler interface { + HandleError(w http.ResponseWriter, r *http.Request, rl *rule.Rule, handleErr error) + HandleRequest(r *http.Request, rl *rule.Rule) (session *authn.AuthenticationSession, err error) + InitializeAuthnSession(r *http.Request, rl *rule.Rule) *authn.AuthenticationSession +} + +type requestHandler struct { r requestHandlerRegistry c configuration.Provider } @@ -60,12 +66,12 @@ type whenConfig struct { When pe.Whens `json:"when"` } -func NewRequestHandler(r requestHandlerRegistry, c configuration.Provider) *RequestHandler { - return &RequestHandler{r: r, c: c} +func NewRequestHandler(r requestHandlerRegistry, c configuration.Provider) RequestHandler { + return &requestHandler{r: r, c: c} } // matchesWhen -func (d *RequestHandler) matchesWhen(w http.ResponseWriter, r *http.Request, h pe.Handler, config json.RawMessage, handleErr error) error { +func (d *requestHandler) matchesWhen(w http.ResponseWriter, r *http.Request, h pe.Handler, config json.RawMessage, handleErr error) error { var when whenConfig if err := d.c.ErrorHandlerConfig(h.GetID(), config, &when); err != nil { d.r.Writer().WriteError(w, r, pe.NewErrErrorHandlerMisconfigured(h, err)) @@ -83,7 +89,7 @@ func (d *RequestHandler) matchesWhen(w http.ResponseWriter, r *http.Request, h p return nil } -func (d *RequestHandler) HandleError(w http.ResponseWriter, r *http.Request, rl *rule.Rule, handleErr error) { +func (d *requestHandler) HandleError(w http.ResponseWriter, r *http.Request, rl *rule.Rule, handleErr error) { if rl == nil { // Create a new, empty rule. rl = new(rule.Rule) @@ -167,7 +173,7 @@ func (d *RequestHandler) HandleError(w http.ResponseWriter, r *http.Request, rl } } -func (d *RequestHandler) HandleRequest(r *http.Request, rl *rule.Rule) (session *authn.AuthenticationSession, err error) { +func (d *requestHandler) HandleRequest(r *http.Request, rl *rule.Rule) (session *authn.AuthenticationSession, err error) { var found bool fields := map[string]interface{}{ @@ -333,7 +339,7 @@ func (d *RequestHandler) HandleRequest(r *http.Request, rl *rule.Rule) (session } // InitializeAuthnSession creates an authentication session and initializes it with a Match context if possible -func (d *RequestHandler) InitializeAuthnSession(r *http.Request, rl *rule.Rule) *authn.AuthenticationSession { +func (d *requestHandler) InitializeAuthnSession(r *http.Request, rl *rule.Rule) *authn.AuthenticationSession { session := &authn.AuthenticationSession{ Subject: "", From b246c29f51e7c27cce4a928087c8bd8bcfd965f1 Mon Sep 17 00:00:00 2001 From: Dimitrij Drus Date: Thu, 3 Feb 2022 17:38:48 +0100 Subject: [PATCH 08/17] constants for headers --- pipeline/errors/error_redirect.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/pipeline/errors/error_redirect.go b/pipeline/errors/error_redirect.go index 3083829502..45b96ef6a8 100644 --- a/pipeline/errors/error_redirect.go +++ b/pipeline/errors/error_redirect.go @@ -13,6 +13,12 @@ import ( var _ Handler = new(ErrorRedirect) +const ( + xForwardedProto = "X-Forwarded-Proto" + xForwardedHost = "X-Forwarded-Host" + xForwardedUri = "X-Forwarded-Uri" +) + type ( ErrorRedirectConfig struct { To string `json:"to"` @@ -42,13 +48,13 @@ func (a *ErrorRedirect) Handle(w http.ResponseWriter, r *http.Request, config js } var scheme, host, requestUri string - if scheme = r.Header.Get("X-Forwarded-Proto"); scheme == "" { + if scheme = r.Header.Get(xForwardedProto); scheme == "" { scheme = r.URL.Scheme } - if host = r.Header.Get("X-Forwarded-Host"); host == "" { + if host = r.Header.Get(xForwardedHost); host == "" { host = r.URL.Host } - if requestUri = r.Header.Get("X-Forwarded-Uri"); requestUri == "" { + if requestUri = r.Header.Get(xForwardedUri); requestUri == "" { requestUri = r.URL.RequestURI() } From 73ad7a32aeca203930fcc5809f1f19df9a018ed9 Mon Sep 17 00:00:00 2001 From: Dimitrij Drus Date: Thu, 3 Feb 2022 17:52:46 +0100 Subject: [PATCH 09/17] moved creation of the redirect url to its own method --- pipeline/errors/error_redirect.go | 37 ++++++++++++++----------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/pipeline/errors/error_redirect.go b/pipeline/errors/error_redirect.go index 45b96ef6a8..6c931e7cfc 100644 --- a/pipeline/errors/error_redirect.go +++ b/pipeline/errors/error_redirect.go @@ -2,7 +2,6 @@ package errors import ( "encoding/json" - "fmt" "net/http" "net/url" @@ -47,30 +46,28 @@ func (a *ErrorRedirect) Handle(w http.ResponseWriter, r *http.Request, config js return err } - var scheme, host, requestUri string - if scheme = r.Header.Get(xForwardedProto); scheme == "" { - scheme = r.URL.Scheme + http.Redirect(w, r, a.RedirectURL(a.extractRedirectURL(r), c), c.Code) + return nil +} + +func (a *ErrorRedirect) extractRedirectURL(req *http.Request) *url.URL { + var scheme, host, path string + if scheme = req.Header.Get(xForwardedProto); scheme == "" { + scheme = req.URL.Scheme } - if host = r.Header.Get(xForwardedHost); host == "" { - host = r.URL.Host + if host = req.Header.Get(xForwardedHost); host == "" { + host = req.URL.Host } - if requestUri = r.Header.Get(xForwardedUri); requestUri == "" { - requestUri = r.URL.RequestURI() + if path = req.Header.Get(xForwardedUri); path == "" { + path = req.URL.Path } - var uri *url.URL - if scheme == "" || host == "" { - // FIXME: I don't think this is applicable for real requests. It is however used by tests. - uri, err = url.Parse(fmt.Sprintf("%s", requestUri)) - } else { - uri, err = url.Parse(fmt.Sprintf("%s://%s%s", scheme, host, requestUri)) - } - if err != nil { - return err - } + u := *req.URL + u.Scheme = scheme + u.Host = host + u.Path = path - http.Redirect(w, r, a.RedirectURL(uri, c), c.Code) - return nil + return &u } func (a *ErrorRedirect) Validate(config json.RawMessage) error { From 006943abb2abe8daa6066de991ef7917565f5149 Mon Sep 17 00:00:00 2001 From: Dimitrij Drus Date: Thu, 3 Feb 2022 19:03:38 +0100 Subject: [PATCH 10/17] utility functions added and used to remove boilerplate code --- api/decision.go | 28 +++++----------------------- pipeline/errors/error_redirect.go | 21 +++++---------------- x/compare.go | 15 +++++++++++++++ 3 files changed, 25 insertions(+), 39 deletions(-) create mode 100644 x/compare.go diff --git a/api/decision.go b/api/decision.go index 2567a46525..5b0fab66cd 100644 --- a/api/decision.go +++ b/api/decision.go @@ -58,29 +58,11 @@ func NewJudgeHandler(r decisionHandlerRegistry) *DecisionHandler { func (h *DecisionHandler) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { if len(r.URL.Path) >= len(DecisionPath) && r.URL.Path[:len(DecisionPath)] == DecisionPath { - var method, scheme, host, path string - - if method = r.Header.Get(xForwardedMethod); method == "" { - method = r.Method - } - if scheme = r.Header.Get(xForwardedProto); scheme == "" { - if r.TLS != nil { - scheme = "https" - } else { - scheme = "http" - } - } - if host = r.Header.Get(xForwardedHost); host == "" { - host = r.Host - } - if path = r.Header.Get(xForwardedUri); path == "" { - path = r.URL.Path[len(DecisionPath):] - } - - r.Method = method - r.URL.Scheme = scheme - r.URL.Host = host - r.URL.Path = path + r.Method = x.OrDefaultString(r.Header.Get(xForwardedMethod), r.Method) + r.URL.Scheme = x.OrDefaultString(r.Header.Get(xForwardedProto), + x.IfThenElseString(r.TLS != nil, "https", "http")) + r.URL.Host = x.OrDefaultString(r.Header.Get(xForwardedHost), r.Host) + r.URL.Path = x.OrDefaultString(r.Header.Get(xForwardedUri), r.URL.Path[len(DecisionPath):]) h.decisions(w, r) } else { diff --git a/pipeline/errors/error_redirect.go b/pipeline/errors/error_redirect.go index 6c931e7cfc..66c41d0e16 100644 --- a/pipeline/errors/error_redirect.go +++ b/pipeline/errors/error_redirect.go @@ -46,26 +46,15 @@ func (a *ErrorRedirect) Handle(w http.ResponseWriter, r *http.Request, config js return err } - http.Redirect(w, r, a.RedirectURL(a.extractRedirectURL(r), c), c.Code) + http.Redirect(w, r, a.RedirectURL(a.extractURL(r), c), c.Code) return nil } -func (a *ErrorRedirect) extractRedirectURL(req *http.Request) *url.URL { - var scheme, host, path string - if scheme = req.Header.Get(xForwardedProto); scheme == "" { - scheme = req.URL.Scheme - } - if host = req.Header.Get(xForwardedHost); host == "" { - host = req.URL.Host - } - if path = req.Header.Get(xForwardedUri); path == "" { - path = req.URL.Path - } - +func (a *ErrorRedirect) extractURL(req *http.Request) *url.URL { u := *req.URL - u.Scheme = scheme - u.Host = host - u.Path = path + u.Scheme = x.OrDefaultString(req.Header.Get(xForwardedProto), req.URL.Scheme) + u.Host = x.OrDefaultString(req.Header.Get(xForwardedHost), req.Host) + u.Path = x.OrDefaultString(req.Header.Get(xForwardedUri), req.URL.Path) return &u } diff --git a/x/compare.go b/x/compare.go new file mode 100644 index 0000000000..97c29d3422 --- /dev/null +++ b/x/compare.go @@ -0,0 +1,15 @@ +package x + +func OrDefaultString(val, defaultVal string) string { + if val == "" { + return defaultVal + } + return val +} + +func IfThenElseString(c bool, thenVal, elseVal string) string { + if c { + return thenVal + } + return elseVal +} From 6e2983b8b1ac72a273f113f57c6927faf8c23dfb Mon Sep 17 00:00:00 2001 From: Dimitrij Drus Date: Thu, 3 Feb 2022 19:09:16 +0100 Subject: [PATCH 11/17] moved the code to update the url back to the Handle method to avoid side effect confusions --- pipeline/errors/error_redirect.go | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/pipeline/errors/error_redirect.go b/pipeline/errors/error_redirect.go index 66c41d0e16..bb702816ee 100644 --- a/pipeline/errors/error_redirect.go +++ b/pipeline/errors/error_redirect.go @@ -46,17 +46,12 @@ func (a *ErrorRedirect) Handle(w http.ResponseWriter, r *http.Request, config js return err } - http.Redirect(w, r, a.RedirectURL(a.extractURL(r), c), c.Code) - return nil -} + r.URL.Scheme = x.OrDefaultString(r.Header.Get(xForwardedProto), r.URL.Scheme) + r.URL.Host = x.OrDefaultString(r.Header.Get(xForwardedHost), r.Host) + r.URL.Path = x.OrDefaultString(r.Header.Get(xForwardedUri), r.URL.Path) -func (a *ErrorRedirect) extractURL(req *http.Request) *url.URL { - u := *req.URL - u.Scheme = x.OrDefaultString(req.Header.Get(xForwardedProto), req.URL.Scheme) - u.Host = x.OrDefaultString(req.Header.Get(xForwardedHost), req.Host) - u.Path = x.OrDefaultString(req.Header.Get(xForwardedUri), req.URL.Path) - - return &u + http.Redirect(w, r, a.RedirectURL(r.URL, c), c.Code) + return nil } func (a *ErrorRedirect) Validate(config json.RawMessage) error { From c0be2cee4b501ebe65adce09abfc705d99e3dce0 Mon Sep 17 00:00:00 2001 From: Dimitrij Drus Date: Thu, 3 Feb 2022 19:11:59 +0100 Subject: [PATCH 12/17] make format --- api/decision_test.go | 8 ++++---- docs/docs/pipeline/authz.md | 14 ++++++++++---- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/api/decision_test.go b/api/decision_test.go index 02f1e21b85..dcb44a574f 100644 --- a/api/decision_test.go +++ b/api/decision_test.go @@ -33,20 +33,20 @@ import ( "testing" "github.com/julienschmidt/httprouter" - "github.com/ory/herodot" - "github.com/ory/oathkeeper/pipeline/authn" - "github.com/ory/oathkeeper/proxy" - "github.com/ory/x/logrusx" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/urfave/negroni" + "github.com/ory/herodot" "github.com/ory/oathkeeper/api" "github.com/ory/oathkeeper/driver/configuration" "github.com/ory/oathkeeper/internal" + "github.com/ory/oathkeeper/pipeline/authn" + "github.com/ory/oathkeeper/proxy" "github.com/ory/oathkeeper/rule" "github.com/ory/viper" + "github.com/ory/x/logrusx" ) func TestDecisionAPI(t *testing.T) { diff --git a/docs/docs/pipeline/authz.md b/docs/docs/pipeline/authz.md index cf60286b73..369137f60c 100644 --- a/docs/docs/pipeline/authz.md +++ b/docs/docs/pipeline/authz.md @@ -297,8 +297,11 @@ if it returns a "403 Forbidden" response code, the access is denied. headers on this list will be forward to upstream services. - `retry` (object, optional) - Configures timeout and delay settings for the request against the token endpoint - - `give_up_after` (string) max delay duration of retry. The value will be parsed by the Go [duration parser](https://pkg.go.dev/time#ParseDuration). - - `max_delay` (string) time to wait between retries and max service response time. The value will be parsed by the Go [duration parser](https://pkg.go.dev/time#ParseDuration). + - `give_up_after` (string) max delay duration of retry. The value will be + parsed by the Go [duration parser](https://pkg.go.dev/time#ParseDuration). + - `max_delay` (string) time to wait between retries and max service response + time. The value will be parsed by the Go + [duration parser](https://pkg.go.dev/time#ParseDuration). #### Example @@ -388,8 +391,11 @@ Forbidden" response code, the access is denied. headers on this list will be forward to upstream services. - `retry` (object, optional) - Configures timeout and delay settings for the request against the token endpoint - - `give_up_after` (string) max delay duration of retry. The value will be parsed by the Go [duration parser](https://pkg.go.dev/time#ParseDuration). - - `max_delay` (string) time to wait between retries and max service response time. The value will be parsed by the Go [duration parser](https://pkg.go.dev/time#ParseDuration). + - `give_up_after` (string) max delay duration of retry. The value will be + parsed by the Go [duration parser](https://pkg.go.dev/time#ParseDuration). + - `max_delay` (string) time to wait between retries and max service response + time. The value will be parsed by the Go + [duration parser](https://pkg.go.dev/time#ParseDuration). #### Example From 2697626f5fedeabfcdf716b3572a2ac4608a1690 Mon Sep 17 00:00:00 2001 From: Dimitrij Drus Date: Fri, 4 Feb 2022 08:46:37 +0100 Subject: [PATCH 13/17] new tests for error_redirect --- pipeline/errors/error_redirect.go | 2 +- pipeline/errors/error_redirect_test.go | 78 ++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/pipeline/errors/error_redirect.go b/pipeline/errors/error_redirect.go index bb702816ee..9235df1659 100644 --- a/pipeline/errors/error_redirect.go +++ b/pipeline/errors/error_redirect.go @@ -47,7 +47,7 @@ func (a *ErrorRedirect) Handle(w http.ResponseWriter, r *http.Request, config js } r.URL.Scheme = x.OrDefaultString(r.Header.Get(xForwardedProto), r.URL.Scheme) - r.URL.Host = x.OrDefaultString(r.Header.Get(xForwardedHost), r.Host) + r.URL.Host = x.OrDefaultString(r.Header.Get(xForwardedHost), r.URL.Host) r.URL.Path = x.OrDefaultString(r.Header.Get(xForwardedUri), r.URL.Path) http.Redirect(w, r, a.RedirectURL(r.URL, c), c.Code) diff --git a/pipeline/errors/error_redirect_test.go b/pipeline/errors/error_redirect_test.go index f6e929095f..802ecd83d7 100644 --- a/pipeline/errors/error_redirect_test.go +++ b/pipeline/errors/error_redirect_test.go @@ -178,3 +178,81 @@ func TestErrorRedirect(t *testing.T) { } }) } + +func TestErrorReturnToRedirectURLHeaderUsage(t *testing.T) { + conf := internal.NewConfigurationWithDefaults() + reg := internal.NewRegistry(conf) + + defaultUrl := &url.URL{Scheme: "http", Host: "ory.sh", Path: "/foo"} + defaultTransform := func(req *http.Request) {} + config := `{"to":"http://test/test","return_to_query_param":"return_to"}` + + a, err := reg.PipelineErrorHandler("redirect") + require.NoError(t, err) + assert.Equal(t, "redirect", a.GetID()) + + for _, tc := range []struct { + name string + expectedUrl *url.URL + transform func(req *http.Request) + }{ + { + name: "all arguments are taken from the url and request method", + expectedUrl: defaultUrl, + transform: defaultTransform, + }, + { + name: "all arguments are taken from the headers", + expectedUrl: &url.URL{Scheme: "https", Host: "test.dev", Path: "/bar"}, + transform: func(req *http.Request) { + req.Header.Add("X-Forwarded-Proto", "https") + req.Header.Add("X-Forwarded-Host", "test.dev") + req.Header.Add("X-Forwarded-Uri", "/bar") + }, + }, + { + name: "only scheme is taken from the headers", + expectedUrl: &url.URL{Scheme: "https", Host: defaultUrl.Host, Path: defaultUrl.Path}, + transform: func(req *http.Request) { + req.Header.Add("X-Forwarded-Proto", "https") + }, + }, + { + name: "only host is taken from the headers", + expectedUrl: &url.URL{Scheme: defaultUrl.Scheme, Host: "test.dev", Path: defaultUrl.Path}, + transform: func(req *http.Request) { + req.Header.Add("X-Forwarded-Host", "test.dev") + }, + }, + { + name: "only path is taken from the headers", + expectedUrl: &url.URL{Scheme: defaultUrl.Scheme, Host: defaultUrl.Host, Path: "/bar"}, + transform: func(req *http.Request) { + req.Header.Add("X-Forwarded-Uri", "/bar") + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + w := httptest.NewRecorder() + r := httptest.NewRequest("GET", defaultUrl.String(), nil) + tc.transform(r) + + err = a.Handle(w, r, json.RawMessage(config), nil, nil) + assert.NoError(t, err) + + loc := w.Header().Get("Location") + assert.NotEmpty(t, loc) + + locUrl, err := url.Parse(loc) + assert.NoError(t, err) + + returnTo := locUrl.Query().Get("return_to") + assert.NotEmpty(t, returnTo) + + returnToUrl, err := url.Parse(returnTo) + assert.NoError(t, err) + + assert.Equal(t, tc.expectedUrl, returnToUrl) + }) + } +} From 923ad32df3de41ca59144afd69c8c9d4de42c000 Mon Sep 17 00:00:00 2001 From: Dimitrij Drus Date: Fri, 4 Feb 2022 08:58:41 +0100 Subject: [PATCH 14/17] added traefik proxy to the supported integrations --- docs/docs/index.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/docs/index.md b/docs/docs/index.md index 6241d88869..15e7f71de7 100644 --- a/docs/docs/index.md +++ b/docs/docs/index.md @@ -28,6 +28,8 @@ API works with [Custom Authorizers](https://aws.amazon.com/de/blogs/compute/introducing-custom-authorizers-in-amazon-api-gateway/) - [Nginx](https://www.nginx.com) via [Authentication Based on Subrequest Result](https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-subrequest-authentication/) +- [Traefik Proxy](https://traefik.io/traefik/) via the + [ForwardAuth Middleware](https://doc.traefik.io/traefik/middlewares/http/forwardauth/) among others. From 35e980da75c5cfb297f10ec320dbe6436d68e4e8 Mon Sep 17 00:00:00 2001 From: Dimitrij Drus Date: Fri, 4 Feb 2022 12:50:53 +0100 Subject: [PATCH 15/17] build command fixed --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 8bcc218dac..11a61536fd 100644 --- a/docs/README.md +++ b/docs/README.md @@ -23,7 +23,7 @@ Most changes are reflected live without having to restart the server. ### Build ``` -$ npm build +$ npm run build ``` This command generates static content into the `build` directory and can be From 8e2626182fc38f65551966e68b952c4f4ac83675 Mon Sep 17 00:00:00 2001 From: Dimitrij Drus Date: Fri, 4 Feb 2022 12:51:15 +0100 Subject: [PATCH 16/17] doc about trafic integration --- docs/docs/guides/traefik-proxy-integration.md | 34 +++++++++++++++++++ docs/sidebar.json | 2 +- 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 docs/docs/guides/traefik-proxy-integration.md diff --git a/docs/docs/guides/traefik-proxy-integration.md b/docs/docs/guides/traefik-proxy-integration.md new file mode 100644 index 0000000000..12a21fa481 --- /dev/null +++ b/docs/docs/guides/traefik-proxy-integration.md @@ -0,0 +1,34 @@ +--- +id: traefik-proxy-integration +title: Traefik Proxy Integration +--- + +[Traefik Proxy](https://doc.traefik.io/traefik/) is modern HTTP proxy and load balancer for microservices, oathkeeper can be integrated with via the [ForwardAuth Middleware](https://doc.traefik.io/traefik/middlewares/http/forwardauth/) by making use of the available [Access Control Decision API](index.md#access-control-decision-api). + +To achieve this, +* configure traefik + * to make use of the aforesaid ForwardAuth middleware by setting the `address` property to the decision URL endpoint and + * by including the required header name(s), the oathkeeper sets in the HTTP responses into the `authResponseHeaders` property. +* configure the route of your service to make use of this middleware + +Example (using Docker labels): + +```.yaml +edge-router: + image: traefik + # further configuration + labels: + - traefik.http.middlewares.oathkeeper.forwardauth.address=http://oathkeeper:4456/decisions + - traefik.http.middlewares.oathkeeper.forwardauth.authResponseHeaders=X-Id-Token,Authorization + # further labels + +service: + image: my-service + # further configuration + labels: + - traefik.http.routers.service.middlewares=oathkeeper + # further labels +``` + + + diff --git a/docs/sidebar.json b/docs/sidebar.json index d04a51075d..0ab14691e4 100644 --- a/docs/sidebar.json +++ b/docs/sidebar.json @@ -19,7 +19,7 @@ ] }, { - "Guides": ["configure-deploy"] + "Guides": ["configure-deploy", "guides/traefik-proxy-integration"] }, "reference/api", { From 6e91ee0648696fe8f47c9cad694669177f944fe3 Mon Sep 17 00:00:00 2001 From: Dimitrij Drus Date: Fri, 4 Feb 2022 13:00:24 +0100 Subject: [PATCH 17/17] more docu --- docs/docs/guides/traefik-proxy-integration.md | 20 +++++++++++-------- docs/docs/index.md | 11 ++++++---- docs/docs/pipeline/error.md | 5 ++++- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/docs/docs/guides/traefik-proxy-integration.md b/docs/docs/guides/traefik-proxy-integration.md index 12a21fa481..cb20d6f968 100644 --- a/docs/docs/guides/traefik-proxy-integration.md +++ b/docs/docs/guides/traefik-proxy-integration.md @@ -3,13 +3,20 @@ id: traefik-proxy-integration title: Traefik Proxy Integration --- -[Traefik Proxy](https://doc.traefik.io/traefik/) is modern HTTP proxy and load balancer for microservices, oathkeeper can be integrated with via the [ForwardAuth Middleware](https://doc.traefik.io/traefik/middlewares/http/forwardauth/) by making use of the available [Access Control Decision API](index.md#access-control-decision-api). +[Traefik Proxy](https://doc.traefik.io/traefik/) is modern HTTP proxy and load +balancer for microservices, oathkeeper can be integrated with via the +[ForwardAuth Middleware](https://doc.traefik.io/traefik/middlewares/http/forwardauth/) +by making use of the available +[Access Control Decision API](index.md#access-control-decision-api). To achieve this, -* configure traefik - * to make use of the aforesaid ForwardAuth middleware by setting the `address` property to the decision URL endpoint and - * by including the required header name(s), the oathkeeper sets in the HTTP responses into the `authResponseHeaders` property. -* configure the route of your service to make use of this middleware + +- configure traefik + - to make use of the aforesaid ForwardAuth middleware by setting the `address` + property to the decision URL endpoint and + - by including the required header name(s), the oathkeeper sets in the HTTP + responses into the `authResponseHeaders` property. +- configure the route of your service to make use of this middleware Example (using Docker labels): @@ -29,6 +36,3 @@ service: - traefik.http.routers.service.middlewares=oathkeeper # further labels ``` - - - diff --git a/docs/docs/index.md b/docs/docs/index.md index 15e7f71de7..83328e48ba 100644 --- a/docs/docs/index.md +++ b/docs/docs/index.md @@ -188,10 +188,13 @@ X-User-ID: john.doe The decision engine allows to configure how ORY Oathkeeper authorizes HTTP requests. Authorization happens in four steps, each of which can be configured: -1. **Access Rule Matching:** Verifies that the HTTP method, path, and host of - the incoming HTTP request conform to your access rules. The request is denied - if no access rules match. The configuration of the matching access rule - becomes the input for the next steps. +1. **Access Rule Matching:** Verifies that the HTTP method, path, scheme, and + host of the incoming HTTP request conform to your access rules. The + information is taken either from the URL, or from the `X-Forwarded-Method`, + `X-Forwarded-Proto`, `X-Forwarded-Host`, `X-Forwarded-Uri` headers (if + present) of the incoming request. The request is denied if no access rules + match. The configuration of the matching access rule becomes the input for + the next steps. 2. **Authentication:** Oathkeeper can validate credentials via a variety of methods like Bearer Token, Basic Authorization, or cookie. Invalid credentials result in denial of the request. The "internal" session state diff --git a/docs/docs/pipeline/error.md b/docs/docs/pipeline/error.md index 901247895e..62884db998 100644 --- a/docs/docs/pipeline/error.md +++ b/docs/docs/pipeline/error.md @@ -415,7 +415,10 @@ conditions under the `when` key. If you want to append the current url (where the error happened) to address redirected to, You can specify `return_to_query_param` to set the name of -parameter that will hold the url. +parameter that will hold the url. The information about the current url is taken +either from the URL, or from the `X-Forwarded-Method`, `X-Forwarded-Proto`, +`X-Forwarded-Host`, `X-Forwarded-Uri` headers (if present) of the incoming +request. **Example**