Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TT-12741] Looped ap is wrongfully inherit the caller's authentication key when using url rewrite #6778

Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ctx/ctx.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ const (
// CacheOptions holds cache options required for cache writer middleware.
CacheOptions
OASDefinition
SelfLooping
)

func ctxSetSession(r *http.Request, s *user.SessionState, scheduleUpdate bool, hashKey bool) {
Expand Down
7 changes: 7 additions & 0 deletions gateway/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ import (
"sync"
"time"

"github.com/TykTechnologies/tyk/internal/httpctx"

gqlv2 "github.com/TykTechnologies/graphql-go-tools/v2/pkg/graphql"

"github.com/getkin/kin-openapi/openapi3"
Expand Down Expand Up @@ -3176,6 +3178,11 @@ func ctxSetCheckLoopLimits(r *http.Request, b bool) {

// Should we check Rate limits and Quotas?
func ctxCheckLimits(r *http.Request) bool {
// If this is a self loop, do not need to check the limits and quotas.
if httpctx.IsSelfLooping(r) {
return false
}

// If looping disabled, allow all
if !ctxLoopingEnabled(r) {
return true
Expand Down
3 changes: 3 additions & 0 deletions gateway/api_loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import (
"sync"
texttemplate "text/template"

"github.com/TykTechnologies/tyk/internal/httpctx"

"github.com/gorilla/mux"
"github.com/justinas/alice"
"github.com/rs/cors"
Expand Down Expand Up @@ -599,6 +601,7 @@ func (d *DummyProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {

var handler http.Handler
if r.URL.Hostname() == "self" {
httpctx.SetSelfLooping(r, true)
titpetric marked this conversation as resolved.
Show resolved Hide resolved
if h, found := d.Gw.apisHandlesByID.Load(d.SH.Spec.APIID); found {
if chain, ok := h.(*ChainObject); ok {
handler = chain.ThisHandler
Expand Down
94 changes: 94 additions & 0 deletions gateway/looping_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package gateway

import (
"encoding/json"
"net/http"
"sync"
"testing"

Expand Down Expand Up @@ -324,7 +325,100 @@ func TestLooping(t *testing.T) {
{Path: "/external/", Code: 200},
}...)
})
}

func TestLooping_AnotherAPIWithAuthTokens(t *testing.T) {
ts := StartTest(nil)
defer ts.Close()

// Looping to another api with auth tokens
specs := ts.Gw.BuildAndLoadAPI(func(spec *APISpec) {
spec.APIDefinition.APIID = "apia"
spec.APIDefinition.Name = "ApiA"
spec.APIDefinition.Proxy.ListenPath = "/apia"
spec.APIDefinition.UseKeylessAccess = false
spec.APIDefinition.AuthConfigs = map[string]apidef.AuthConfig{
"authToken": {
AuthHeaderName: "Authorization",
},
}
UpdateAPIVersion(spec, "Default", func(v *apidef.VersionInfo) {
v.UseExtendedPaths = true
v.ExtendedPaths.URLRewrite = []apidef.URLRewriteMeta{{
Path: "/",
Method: http.MethodGet,
MatchPattern: ".*",
RewriteTo: "tyk://apib",
}}
})
}, func(spec *APISpec) {
spec.APIDefinition.APIID = "apib"
spec.APIDefinition.Name = "ApiB"
spec.APIDefinition.Proxy.ListenPath = "/apib"
spec.APIDefinition.UseKeylessAccess = false
spec.APIDefinition.AuthConfigs = map[string]apidef.AuthConfig{
"authToken": {
AuthHeaderName: "X-Api-Key",
},
}
})
specApiA := specs[0]
specApiB := specs[1]

_, authKeyForApiA := ts.CreateSession(func(s *user.SessionState) {
s.AccessRights = map[string]user.AccessDefinition{
specApiA.APIDefinition.APIID: {
APIName: specApiA.APIDefinition.Name,
APIID: specApiA.APIDefinition.APIID,
Versions: []string{"default"},
AllowanceScope: specApiA.APIDefinition.APIID,
},
}
s.OrgID = specApiA.APIDefinition.OrgID
})

_, authKeyForApiB := ts.CreateSession(func(s *user.SessionState) {
s.AccessRights = map[string]user.AccessDefinition{
specApiB.APIDefinition.APIID: {
APIName: specApiB.APIDefinition.Name,
APIID: specApiB.APIDefinition.APIID,
Versions: []string{"default"},
AllowanceScope: specApiB.APIDefinition.APIID,
},
}
s.OrgID = specApiB.APIDefinition.OrgID
})

headersWithApiBToken := map[string]string{
"Authorization": authKeyForApiA,
"X-Api-Key": authKeyForApiB,
}
headersWithoutApiBToken := map[string]string{
"Authorization": authKeyForApiA,
"X-Api-Key": "some-string",
}
headersWithOnlyApiAToken := map[string]string{
"Authorization": authKeyForApiA,
}
_, _ = ts.Run(t, []test.TestCase{
{
Headers: headersWithApiBToken,
Path: "/apia",
Code: http.StatusOK,
},
{
Headers: headersWithoutApiBToken,
Path: "/apia",
Code: http.StatusForbidden,
BodyMatch: "Access to this API has been disallowed",
},
{
Headers: headersWithOnlyApiAToken,
Path: "/apia",
Code: http.StatusUnauthorized,
BodyMatch: "Authorization field missing",
},
}...)
}

func TestConcurrencyReloads(t *testing.T) {
Expand Down
1 change: 1 addition & 0 deletions gateway/middleware_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,7 @@ func TestQuotaNotAppliedWithURLRewrite(t *testing.T) {
spec.Proxy.ListenPath = "/quota-test"
spec.UseKeylessAccess = false
UpdateAPIVersion(spec, "Default", func(v *apidef.VersionInfo) {
v.UseExtendedPaths = true
v.ExtendedPaths.URLRewrite = []apidef.URLRewriteMeta{{
Path: "/abc",
Method: http.MethodGet,
Expand Down
4 changes: 3 additions & 1 deletion gateway/mw_auth_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"strings"
"time"

"github.com/TykTechnologies/tyk/internal/httpctx"

"github.com/TykTechnologies/tyk/internal/crypto"
"github.com/TykTechnologies/tyk/internal/otel"
"github.com/TykTechnologies/tyk/storage"
Expand Down Expand Up @@ -95,7 +97,7 @@ func (k *AuthKey) ProcessRequest(_ http.ResponseWriter, r *http.Request, _ inter
}

// skip auth key check if the request is looped.
titpetric marked this conversation as resolved.
Show resolved Hide resolved
if ses := ctxGetSession(r); ses != nil && !ctxCheckLimits(r) {
if ses := ctxGetSession(r); ses != nil && httpctx.IsSelfLooping(r) {
titpetric marked this conversation as resolved.
Show resolved Hide resolved
return nil, http.StatusOK
}

Expand Down
14 changes: 14 additions & 0 deletions internal/httpctx/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package httpctx
import (
"context"
"net/http"

"github.com/TykTechnologies/tyk/ctx"
)

type Value[T any] struct {
Expand All @@ -26,3 +28,15 @@ func (v *Value[T]) Set(r *http.Request, val T) *http.Request {
*r = *h
return h
}

var selfLoopingValue = NewValue[bool](ctx.SelfLooping)
titpetric marked this conversation as resolved.
Show resolved Hide resolved
titpetric marked this conversation as resolved.
Show resolved Hide resolved

// SetSelfLooping updates the request context with a boolean value indicating whether the request is in a self-looping state.
func SetSelfLooping(r *http.Request, value bool) {
selfLoopingValue.Set(r, value)
}

// IsSelfLooping returns true if the request is flagged as self-looping, indicating it originates and targets the same service.
func IsSelfLooping(r *http.Request) bool {
return selfLoopingValue.Get(r)
}
20 changes: 20 additions & 0 deletions internal/httpctx/context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,23 @@ func TestValue_SetDifferentTypes(t *testing.T) {
retrievedInt := intValue.Get(req)
assert.Equal(t, expectedInt, retrievedInt, "Retrieved int value does not match expected value")
}

func TestSetSelfLooping(t *testing.T) {
// Create a new HTTP request using httptest
req := httptest.NewRequest("GET", "/", nil)
httpctx.SetSelfLooping(req, true)

// Expected value from the context is true
expectedBool := httpctx.IsSelfLooping(req)
assert.True(t, expectedBool)
titpetric marked this conversation as resolved.
Show resolved Hide resolved
}

func TestIsSelfLooping(t *testing.T) {
// Create a new HTTP request using httptest
req := httptest.NewRequest("GET", "/", nil)

// Expected value from the context is false because we didn't call
// SetSelfLooping before.
expectedBool := httpctx.IsSelfLooping(req)
assert.False(t, expectedBool)
}
titpetric marked this conversation as resolved.
Show resolved Hide resolved
Loading