Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion .github/workflows/jira-pr-validator.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Validate Jira ticket
uses: TykTechnologies/jira-linter@main
uses: TykTechnologies/jira-linter@d4f84b2ca8520dbb52971fbdc8c82a91dbaf18d5
with:
jira-base-url: 'https://tyktech.atlassian.net'
jira-api-token: ${{ secrets.JIRA_TOKEN }}
30 changes: 15 additions & 15 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ jobs:
(needs.dep-guard.result == 'success' || needs.dep-guard.result == 'skipped') &&
github.event.pull_request.draft == false
name: '${{ matrix.golang_cross }}'
runs-on: ${{ vars.DEFAULT_RUNNER }}
runs-on: ${{ vars.BUILD_RUNNER || vars.DEFAULT_RUNNER }}
permissions:
id-token: write # AWS OIDC JWT
contents: read # actions/checkout
Expand Down Expand Up @@ -163,7 +163,7 @@ jobs:
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
with:
context: "dist"
platforms: linux/amd64,linux/arm64,linux/s390x
platforms: ${{ github.event_name == 'pull_request' && 'linux/amd64' || 'linux/amd64,linux/arm64,linux/s390x' }}
file: ci/Dockerfile.distroless
provenance: mode=max
sbom: true
Expand All @@ -176,6 +176,7 @@ jobs:
BUILD_PACKAGE_NAME=tyk-gateway-ee
- name: Docker metadata for ee tag push
id: tag_metadata_ee
if: startsWith(github.ref, 'refs/tags')
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5
with:
images: |
Expand All @@ -193,7 +194,7 @@ jobs:
org.opencontainers.image.vendor=tyk.io
org.opencontainers.image.version=${{ github.ref_name }}
- name: push ee image to prod
if: ${{ matrix.golang_cross == '1.25-bullseye' }}
if: ${{ matrix.golang_cross == '1.25-bullseye' && startsWith(github.ref, 'refs/tags') }}
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
with:
context: "dist"
Expand All @@ -202,15 +203,14 @@ jobs:
provenance: mode=max
sbom: true
cache-from: type=gha
cache-to: type=gha,mode=max
push: ${{ startsWith(github.ref, 'refs/tags') }}
push: true
tags: ${{ steps.tag_metadata_ee.outputs.tags }}
labels: ${{ steps.tag_metadata_ee.outputs.labels }}
build-args: |
BUILD_PACKAGE_NAME=tyk-gateway-ee
- name: Docker metadata for fips CI
id: ci_metadata_fips
if: ${{ matrix.golang_cross == '1.25-bullseye' }}
if: ${{ matrix.golang_cross == '1.25-bullseye' && github.event_name != 'pull_request' }}
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5
with:
images: |
Expand All @@ -225,7 +225,7 @@ jobs:
type=semver,pattern={{major}}.{{minor}},prefix=v
type=semver,pattern={{version}},prefix=v
- name: push fips image to CI
if: ${{ matrix.golang_cross == '1.25-bullseye' }}
if: ${{ matrix.golang_cross == '1.25-bullseye' && github.event_name != 'pull_request' }}
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
with:
context: "dist"
Expand All @@ -243,6 +243,7 @@ jobs:
BASE_IMAGE=tykio/dhi-busybox:1.37-fips
- name: Docker metadata for fips tag push
id: tag_metadata_fips
if: startsWith(github.ref, 'refs/tags')
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5
with:
images: |
Expand All @@ -259,7 +260,7 @@ jobs:
org.opencontainers.image.vendor=tyk.io
org.opencontainers.image.version=${{ github.ref_name }}
- name: push fips image to prod
if: ${{ matrix.golang_cross == '1.25-bullseye' }}
if: ${{ matrix.golang_cross == '1.25-bullseye' && startsWith(github.ref, 'refs/tags') }}
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
with:
context: "dist"
Expand All @@ -268,8 +269,7 @@ jobs:
provenance: mode=max
sbom: true
cache-from: type=gha
cache-to: type=gha,mode=max
push: ${{ startsWith(github.ref, 'refs/tags') }}
push: true
tags: ${{ steps.tag_metadata_fips.outputs.tags }}
labels: ${{ steps.tag_metadata_fips.outputs.labels }}
build-args: |
Expand All @@ -289,7 +289,7 @@ jobs:
fi
- name: Docker metadata for std CI
id: ci_metadata_std
if: ${{ matrix.golang_cross == '1.25-bullseye' }}
if: ${{ matrix.golang_cross == '1.25-bullseye' && github.event_name != 'pull_request' }}
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5
with:
images: |
Expand All @@ -304,7 +304,7 @@ jobs:
type=semver,pattern={{major}}.{{minor}},prefix=v
type=semver,pattern={{version}},prefix=v
- name: push std image to CI
if: ${{ matrix.golang_cross == '1.25-bullseye' }}
if: ${{ matrix.golang_cross == '1.25-bullseye' && github.event_name != 'pull_request' }}
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
with:
context: "dist"
Expand All @@ -321,6 +321,7 @@ jobs:
BUILD_PACKAGE_NAME=tyk-gateway
- name: Docker metadata for std tag push
id: tag_metadata_std
if: startsWith(github.ref, 'refs/tags')
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5
with:
images: |
Expand All @@ -338,7 +339,7 @@ jobs:
org.opencontainers.image.vendor=tyk.io
org.opencontainers.image.version=${{ github.ref_name }}
- name: push std image to prod
if: ${{ matrix.golang_cross == '1.25-bullseye' }}
if: ${{ matrix.golang_cross == '1.25-bullseye' && startsWith(github.ref, 'refs/tags') }}
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
with:
context: "dist"
Expand All @@ -347,8 +348,7 @@ jobs:
provenance: mode=max
sbom: true
cache-from: type=gha
cache-to: type=gha,mode=max
push: ${{ startsWith(github.ref, 'refs/tags') }}
push: true
tags: ${{ steps.tag_metadata_std.outputs.tags }}
labels: ${{ steps.tag_metadata_std.outputs.labels }}
build-args: |
Expand Down
92 changes: 85 additions & 7 deletions gateway/api_definition.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"net/url"
"os"
"path/filepath"
"sort"
"strings"
"sync/atomic"
texttemplate "text/template"
Expand All @@ -23,6 +24,7 @@ import (
"github.com/TykTechnologies/tyk/internal/httpctx"
"github.com/TykTechnologies/tyk/internal/httputil"
"github.com/TykTechnologies/tyk/internal/mcp"
"github.com/TykTechnologies/tyk/internal/oasutil"

"github.com/getkin/kin-openapi/routers/gorillamux"

Expand Down Expand Up @@ -1372,7 +1374,6 @@ func (a APIDefinitionLoader) compileOASValidateRequestPathSpec(apiSpec *APISpec,
continue
}

// Find the path and method for this operation
path, method := a.findPathAndMethodForOperation(apiSpec, operationID)
if path == "" || method == "" {
continue
Expand All @@ -1384,14 +1385,20 @@ func (a APIDefinitionLoader) compileOASValidateRequestPathSpec(apiSpec *APISpec,
OASPath: path,
}

// The path in OAS is relative to the server URL (listenPath)
// For regex matching, we don't prepend listenPath because URLSpec.matchesPath
// will strip the listenPath before matching
// Use standard regex generation with gateway config
a.generateRegex(path, &newSpec, OASValidateRequest, conf)
urlSpec = append(urlSpec, newSpec)
}

urlSpec = a.addStaticPathShields(apiSpec, conf, urlSpec, OASValidateRequest, func(path, method string) URLSpec {
return URLSpec{
OASValidateRequestMeta: &oas.ValidateRequest{Enabled: false},
OASMethod: method,
OASPath: path,
}
})

sortURLSpecsByPathPriority(urlSpec)

return urlSpec
}

Expand All @@ -1417,7 +1424,6 @@ func (a APIDefinitionLoader) compileOASMockResponsePathSpec(apiSpec *APISpec, co
continue
}

// Find the path and method for this operation
path, method := a.findPathAndMethodForOperation(apiSpec, operationID)
if path == "" || method == "" {
continue
Expand All @@ -1429,14 +1435,78 @@ func (a APIDefinitionLoader) compileOASMockResponsePathSpec(apiSpec *APISpec, co
OASPath: path,
}

// Use standard regex generation with gateway config
a.generateRegex(path, &newSpec, OASMockResponse, conf)
urlSpec = append(urlSpec, newSpec)
}

urlSpec = a.addStaticPathShields(apiSpec, conf, urlSpec, OASMockResponse, func(path, method string) URLSpec {
return URLSpec{
OASMockResponseMeta: &oas.MockResponse{Enabled: false},
OASMethod: method,
OASPath: path,
}
})

sortURLSpecsByPathPriority(urlSpec)

return urlSpec
}

// addStaticPathShields adds synthetic disabled URLSpec entries for static OAS paths
// that don't already have an entry in urlSpec. These entries act as shields: when the
// middleware scans the path list, a static shield entry matches before any parameterised
// regex, and the middleware sees Enabled=false and skips it. This prevents parameterised
// paths (e.g. /employees/{id}) from incorrectly matching static paths (e.g. /employees/static).
//
// Shield entries are only added when urlSpec contains at least one parameterised path,
// since without parameterised paths there is no cross-matching risk.
func (a APIDefinitionLoader) addStaticPathShields(
apiSpec *APISpec,
conf config.Config,
urlSpec []URLSpec,
status URLStatus,
newDisabledSpec func(path, method string) URLSpec,
) []URLSpec {
if apiSpec.OAS.Paths == nil {
return urlSpec
}

existing, hasParameterised := indexURLSpecs(urlSpec)
if !hasParameterised {
return urlSpec
}

for path, pathItem := range apiSpec.OAS.Paths.Map() {
if httputil.IsMuxTemplate(path) {
continue
}
for method := range pathItem.Operations() {
method = strings.ToUpper(method)
if _, exists := existing[path+":"+method]; exists {
continue
}
newSpec := newDisabledSpec(path, method)
a.generateRegex(path, &newSpec, status, conf)
urlSpec = append(urlSpec, newSpec)
}
}

return urlSpec
}

// indexURLSpecs builds a set of "path:METHOD" keys from the given specs and reports
// whether any spec uses a parameterised (mux-template) path.
func indexURLSpecs(specs []URLSpec) (existing map[string]struct{}, hasParameterised bool) {
existing = make(map[string]struct{}, len(specs))
for _, spec := range specs {
existing[spec.OASPath+":"+spec.OASMethod] = struct{}{}
if httputil.IsMuxTemplate(spec.OASPath) {
hasParameterised = true
}
}
return
}

// findPathAndMethodForOperation finds the path and method for a given operation ID
// by searching through the OAS paths.
func (a APIDefinitionLoader) findPathAndMethodForOperation(apiSpec *APISpec, operationID string) (string, string) {
Expand All @@ -1455,6 +1525,14 @@ func (a APIDefinitionLoader) findPathAndMethodForOperation(apiSpec *APISpec, ope
return "", ""
}

// sortURLSpecsByPathPriority sorts URLSpec entries using the same path priority
// rules as oasutil.SortByPathLength, ensuring consistent ordering across the gateway.
func sortURLSpecsByPathPriority(specs []URLSpec) {
sort.Slice(specs, func(i, j int) bool {
return oasutil.PathLess(specs[i].OASPath, specs[j].OASPath)
})
}

// extractMCPPrimitivesToPaths extracts MCP primitives (tools, resources, prompts) from the OAS
// definition and populates them into the ExtendedPaths structure for each API version.
// It also adds built-in MCP operation paths (tools/call, resources/read, prompts/get) to
Expand Down
Loading
Loading