tyk Documentation

Open Source API and AI Gateway supporting REST, GraphQL, TCP, gRPC and MCP (Model Context Protocol)

RAW Doc

Docker & Self-Hosted Deployment Verified OCI

Deploy tyk on any server, homelab, or cloud container platform.

1-Click Terminal Command
bash (docker run)
docker run -d --name tyk -p 8080:8080 --restart always redis
Configured Services & Ports
⚙️ redis
image: redis
⚙️ gw
image: ${GATEWAY_IMAGE}
🔌 Ports: 0.0.0.0:8080:8080
⚙️ httpbin.org
image: kennethreitz/httpbin
docker-compose.yml
yaml (docker-compose.yml)
version: "3"
services:
  redis:
    image: redis

  gw:
    image: ${GATEWAY_IMAGE}
    volumes:
      - ./foobar-plugin/foobar-plugin_${plugin_version}_${plugin_os}_${plugin_arch}.so:/opt/tyk-gateway/middleware/foobar-plugin.so
      - ./helloworld-plugin/helloworld-plugin_${plugin_version}_${plugin_os}_${plugin_arch}.so:/opt/tyk-gateway/middleware/helloworld-plugin.so
      - ./helloworld-plugin/helloworld-plugin-1.json:/opt/tyk-gateway/apps/helloworld-plugin-1.json
      - ./helloworld-plugin/helloworld-plugin-2.json:/opt/tyk-gateway/apps/helloworld-plugin-2.json
      - ./foobar-plugin/foobar-plugin-1.json:/opt/tyk-gateway/apps/foorbar-plugin-1.json
      - ./foobar-plugin/foobar-plugin-2.json:/opt/tyk-gateway/apps/foorbar-plugin-2.json
    ports:
      - "0.0.0.0:8080:8080"
    environment:
      - TYK_LOGLEVEL=debug
      - TYK_DB_REDISHOST=redis

  httpbin.org:
    image: kennethreitz/httpbin

Technical Documentation: TykTechnologies/tyk

ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference

Overview (`README.md`)







Documentation | MCP Gateway | AI Gateway | Forum | Blog | About

Tyk API Gateway

Tyk Gateway is the cloud-native, open source, enterprise-ready API and AI Gateway supporting REST, GraphQL, TCP, gRPC and MCP (Model Context Protocol).

Built from the ground up, as the fastest API Gateway on the planet since 2014.

Tyk Gateway is provided ‘Batteries-included’, with no feature lockout. Enabling your organization to rate limit, auth, gather analytics, apply microservice patterns and more with ease.

There are three different ways you can try Tyk:

| Everything you need to manage APIs using Tyk Open Source. Follow the simple Get Started guide below 👇 | The Enterprise API Management platform: Management Control Plane with GUI & Developer Portal.
Tyk Self Managed | The Enterprise API Management SaaS platform: hosted Management Control Plane with GUI & Developer Portal.
Tyk Cloud |

| --- | --- | --- |


Getting Started

Tyk Cloud is the easiest way to set up Tyk. Tyk hosts and manages the full stack for you, so you do not install anything. You can sign up for a free trial of Tyk Cloud and get started in minutes. If not, you can follow the instructions below to get started with Tyk on your own infrastructure.

Run the open source Tyk Gateway with Docker in under 2 minutes. You need Docker and Docker Compose.

Follow the Tyk Open Source Gateway quick start. It shows you how to:

  1. Create a Gateway configuration file (tyk.conf).
  2. On Linux, create an apps folder where the Gateway saves your APIs.
  3. Create a Docker Compose file that runs Tyk Gateway and Redis.
  4. Start the Gateway with docker compose up -d.

Confirm that the Gateway runs. Send a request to the hello endpoint:

console
curl localhost:8080/hello

Output:

json
{"status":"pass","version":"5.x.x","description":"Tyk GW"}

Next, create your first API with the Tyk Gateway API.

Try full API Management Lifecycle

To evaluate the enterprise API management platform on your infrastructure, use Tyk Install. It runs Tyk Gateway, Tyk Dashboard, the Developer Portal, Tyk Pump, Redis and PostgreSQL with Docker Compose, and preloads sample APIs and policies. You need a licence. To get one, request a Tyk Self-Managed trial.

console
git clone https://github.com/TykTechnologies/tyk-install
cd tyk-install/docker/self-managed
cp .env.example .env   # add your licence keys to .env
cd ../getting-started
bash start.sh

For the full steps, see the Tyk Self-Managed quick start.


Other Installations are available:

  1. Docker
  2. Kubernetes with Helm (tyk-oss chart from Tyk Charts)
  3. Ansible
  4. Red Hat and CentOS
  5. Ubuntu
  6. Compile Tyk from Source

Getting started with AI Gateway for LLM and MCP

Tyk Gateway governs MCP traffic from AI agents, applying authentication, rate limiting and per-tool access control to every JSON-RPC call. Tyk AI Studio handles LLM management, with model routing, budgets and guardrails across providers. Start at the AI management overview.

Open Source API Gateway Features

Use any protocol: REST (with native OpenAPI support), SOAP, GraphQL, gRPC, TCP, and MCP.

MCP Gateway: Put the gateway in front of remote Model Context Protocol servers so AI agent traffic is authenticated, rate limited and auditable. Tyk parses JSON-RPC 2.0 and applies policy per tool, resource and prompt rather than treating MCP as opaque HTTP.

API to MCP: Generate an MCP proxy directly from a REST API in Tyk Gateway, without building or hosting a separate MCP server.

Industry Standard Authentication: JWT, bearer Tokens, Basic Auth, Client Certificates and more.

Open API Standards: Import your Swagger and OpenAPI Documents (OAS 2.X and OAS 3.0.1) to scaffold APIs in Tyk.

Ultra performant: Low latency, and thousands of rps with just a single CPU, horizontally and vertically scalable.

Content mediation: Transform all the things, from request or response headers to converting between SOAP and GraphQL.

Extensible Plugin Architecture: Customize Tyk’s middleware chain by writing plugins in your language of choice - from Python to Javascript to Go, or any language which supports gRPC.

Rate Limiting & Quotas: Protect your upstreams from becoming overloaded and/or apply limits for each consumer.

API Versioning - API Versions can be easily set and sunset (deprecated) at a specific time and date.

Granular Access Control - Grant access to one or more APIs on a per version and operation basis.

Blocklist/Allowlist/Ignore endpoint access - Enforce strict security models on a version-by-version basis to your access points.

Analytics logging - Record detailed usage data on who is using your APIs (raw data only)

CORS - Enable CORS for certain APIs so users can make browser-based requests

Webhooks - Trigger webhooks against events such as Quota Violations and Authentication failures

IP AllowListing - Block access to non-trusted IP addresses for more secure interactions

Hitless reloads - Tyk configurations can be altered dynamically and the service restarted without affecting any active request

Kubernetes native declarative API: using Tyk Operator (requires a licence)

Tyk Technologies uses the same API Gateway for all it’s applications. Protecting, securing, and processing APIs for thousands of organizations and businesses around the world. Ideal for Open Banking, building software in the clouds as well as exposing APIs to teams, partners & consumers.

Tyk OSS Integrations

Tyk Technologies maintains other Open Source Software which can be used in conjunction with Tyk API Gateway:

Tyk AI Studio - Open source (AGPL-3.0) AI management platform for LLM traffic: model routing, budgets and cost controls, RAG data sources, tools and agents, with governance across providers including OpenAI, Anthropic, AWS Bedrock, Google Vertex AI, Hugging Face and Ollama. Use it alongside Tyk Gateway when you need LLM and AI management rather than MCP proxying. Docs

Tyk Pump - Pluggable analytics purger to move Analytics generated by your Tyk nodes to any back-end.

Tyk Identity Broker - Tyk Authentication Proxy for third-party login

Tyk Mserv - Asset Server and gRPC host

Documentation

All the documentation for Tyk Gateway and other OSS-related topics can be found at https://tyk.io/docs/tyk-open-source

Community

Licensing

Tyk is dual-licensed:

  1. Open Source License: The code in the root directory and all subdirectories except the 'ee' folder is released under the MPL v2.0. Please see LICENSE for the full version of the open source license.

  2. Commercial License: The code in the 'ee' folder is subject to a commercial license. For more information about obtaining a commercial license, please contact our sales team at [email protected].

Compiling Tyk Gateway

Compile from Source

console
git clone https://github.com/TykTechnologies/tyk
go build

Use the Go version in go.mod to build master, the current development version. Tyk is officially supported on Linux/amd64, Linux/i386 and Linux/arm64.

To run tests locally use the following command:

console
go test ./...

Note that tests require Redis to be running on the same machine (default port).

To write your own test please use this guide https://github.com/TykTechnologies/tyk/blob/master/TESTING.md

Contributing

For more information about contributing PRs and issues, see CONTRIBUTING.md.

—


Overview (`coprocess/README.md`)

Coprocess - PoC

This feature makes it possible to write Tyk middleware using your favorite languages.

Proto files

To change the proto files and update the bindings, see proto/ and
proto/update_bindings.sh.

Python support

Python support is available, more notes here.

Lua support

Lua support is available as well. Notes here.

gRPC support

Tyk provides support for gRPC, you may use any of the gRPC supported languages (e.g. Ruby, Java, etc.). Check the gRPC support README.

ID Extractor & auth cache

The ID extractor is a very useful mechanism that will let you cache your authentication IDs and prevent certain requests from hitting your CP backend. It takes a set of rules from your API configuration (the rules are set per API).

A sample usage will look like this:

json
"custom_middleware": {
  "pre": [
    {
      "name": "MyPreMiddleware",
      "require_session": false
    }
  ],
  "id_extractor": {
    "extract_from": "header",
    "extract_with": "value",
    "extractor_config": {
      "header_name": "Authorization"
    }
  },
  "driver": "grpc"
},

Tyk provides a set of ID extractors that aim to cover the most common use cases, a very simple one is the value extractor.

Interoperability

This feature implements an in-process message passing mechanism, based on Protocol Buffers, any supported languages should provide a function to receive, unmarshal and process this kind of messages.

The main interoperability task is achieved by using cgo as a bridge between a supported language -like Python- and the Go codebase.

Your C bridge function must accept and return a CoProcessMessage data structure like the one described in api.h, where p_data is a pointer to the serialized data and length indicates the length of it.

c
struct CoProcessMessage {
  void* p_data;
  int length;
};

The unpacked data will hold the actual CoProcessObject data structure, where HookType represents the hook type (see below), Request represents the HTTP request and Session is the Tyk session data.

The Spec field holds the API specification data, like organization ID, API ID, etc.

go
type CoProcessObject struct {
	HookType string
	Request  CoProcessMiniRequestObject
	Session  SessionState
	Metadata map[string]string
	Spec     map[string]string
}

Coprocess Dispatcher

coprocess.Dispatcher describes a very simple interface for implementing the dispatcher logic, the required methods are: Dispatch, DispatchEvent and Reload.

Dispatch accepts a pointer to a struct CoProcessObject (as described above) and must return an object of the same type. This method will be called for every configured hook, on every request. Traditionally this method will perform a single function call on the target language side (like Python_DispatchHook in coprocess_python), and the corresponding logic will be handled from there (mostly because different languages have different ways of loading, referencing or calling middlewares).

DispatchEvent provides a way of dispatching Tyk events to a target language. This method doesn't return any variables but does receive a JSON-encoded object containing the event data. For extensibility purposes, this method doesn't use Protocol Buffers, the input is a []byte, the target language will take this (as a char) and perform the JSON decoding operation.

Reload is called when triggering a hot reload, this method could be useful for reloading scripts or modules in the target language.

Coprocess Dispatcher - Hooks

This component is in charge of dispatching your HTTP requests to the custom middlewares, in the right order. The dispatcher follows the standard middleware chain logic and provides a simple mechanism for "hooking" your custom middleware behavior, the supported hooks are:

Pre: gets executed before any authentication information is extracted from the header or parameter list of the request.

Post: gets executed after the authentication, validation, throttling, and quota-limiting middleware has been executed, just before the request is proxied upstream. Use this to post-process a request before sending it to your upstream API.

PostKeyAuth: gets executed right after the autentication process.

CustomAuthCheck: gets executed as a custom authentication middleware, instead of the standard ones provided by Tyk. Use this to provide your own authentication mechanism.

Coprocess Gateway API

coprocess_api.go provides a bridge between the gateway API and C, any function that needs to be exported should have the export keyword:

go
//export TykTriggerEvent
func TykTriggerEvent( CEventName *C.char, CPayload *C.char ) {
  eventName := C.GoString(CEventName)
  payload := C.GoString(CPayload)

  FireSystemEvent(tykcommon.TykEvent(eventName), EventMetaDefault{
    Message: payload,
  })
}

You should also expect a header file declaration of this function in api.h, like this:

c
#ifndef TYK_COPROCESS_API
#define TYK_COPROCESS_API
extern void TykTriggerEvent(char* event_name, char* payload);
#endif

The language binding will include this header file (or declare the function inline) and perform the necessary steps to call it with the appropriate arguments (like a ffi mechanism could do). As a reference, this is how this could be achieved if you're building a Cython module:

python
cdef extern:
  void TykTriggerEvent(char* event_name, char* payload);

def call():
  event_name = 'my event'.encode('utf-8')
  payload = 'my payload'.encode('utf-8')
  TykTriggerEvent( event_name, payload )

Basic usage

The intended way of using a Coprocess middleware is to specify it as part of an API definition:

json
"custom_middleware": {
  "pre": [
      {
          "name": "MyPreMiddleware",  
          "require_session": false
      },
      {
          "name": "AnotherPreMiddleware",
          "require_session": false
      }
  ],
  "post": [
    {
      "name": "MyPostMiddleware",
      "require_session": false
    }
  ],
  "post_key_auth": [
    {
      "name": "MyPostKeyAuthMiddleware",
      "require_session": true
    }
  ],
  "auth_check": {
    "name": "MyAuthCheck"
  },
  "driver": "python"
}

It's important to note that all hook types support chaining except the custom auth check (auth_check).

Build notes

It's possible to use a build tag:

text
go build -tags 'coprocess python'
text
go build -tags 'coprocess somelanguage'

Each language should implement a CoProcessInit function, this will be called from the main function when the coprocess build tag is used.

Using the coprocess build tag with no language tag will fail.

A standard build is still possible:

text
go build

coprocess_dummy.go provides a dummy CoProcessInit function that will be called if you perform a standard Tyk build. This file will be ignored when using the coprocess build tag, as we expect it to be implemented by a language.

Tests

You must use the coprocess build tag to run the tests:

text
go test -tags 'coprocess'
go test -run CoProcess -tags 'coprocess'

References

Trello note


Overview (`apidef/oas/README.md`)

OAS Package

This package provides support for OpenAPI Specification (OAS) schema validation and integration with Tyk's custom extensions.

Overview

The OAS package handles:

  • Loading and validating OAS schemas (3.0, 3.1, and future versions)
  • Injecting Tyk-specific extensions (x-tyk-api-gateway) into OAS schemas
  • Validating OAS documents and templates against schemas
  • Managing schema versions and defaults

How to Add a New OAS Version

When a new OAS version is released (e.g., OAS 4.0), follow these steps to add support:

Step 1: Add the OAS Schema File

  1. Download or create the official JSON Schema for the new OAS version
  2. Save it to apidef/oas/schema/ directory with the naming convention: {major}.{minor}.json
    • Example: 3.1.json for OAS 3.1.x
    • Example: 4.0.json for OAS 4.0.x
bash
# Example
curl -o apidef/oas/schema/4.0.json https://spec.openapis.org/oas/4.0/schema/...

Step 2: Understand Schema Structure Differences

Different OAS/JSON Schema versions may use different keys for definitions:

  • OAS 3.0 uses "definitions" (JSON Schema Draft 04)
  • OAS 3.1+ uses "$defs" (JSON Schema 2020-12)
  • Future versions may introduce new keys

Check the schema file to identify which key is used:

bash
# Check for definitions key
grep -E '"definitions"|"\$defs"' apidef/oas/schema/4.0.json | head -5

Step 3: Update Code (if needed)

The code is designed to automatically detect and handle different definition keys using the GetDefinitionsKey() function.

If the new version uses an existing key (definitions or $defs):

  • ✅ No code changes needed! The schema will load automatically.

If the new version uses a NEW key (e.g., $schemas):

  • Update the GetDefinitionsKey() function in validator.go to detect the new key:
go
func GetDefinitionsKey(schemaData []byte) string {
    // Try newest format first
    if _, _, _, err := jsonparser.Get(schemaData, "new-key"); err == nil {
        return "new-key"
    }
    // Try OAS 3.1+ format
    if _, _, _, err := jsonparser.Get(schemaData, keyDefs); err == nil {
        return keyDefs
    }
    // Fall back to OAS 3.0 format
    return keyDefinitions
}

Step 4: Add Tests

Add comprehensive tests to ensure the new schema loads correctly:

4.1 Update `Test_loadOASSchema`

The test will automatically verify the new version if the schema file exists. It:

  • Checks that the schema loads
  • Verifies x-tyk extensions are injected correctly
  • Validates the correct definitions key is used

4.2 Add Version-Specific Validation Test

Create a new test function to validate documents with the new version:

go
func TestValidateOASObject_4_0(t *testing.T) {
    t.Parallel()
    
    // Create minimal valid OAS 4.0 document
    validOAS40Doc := []byte(`{
        "openapi": "4.0.0",
        "info": {
            "title": "Test API 4.0",
            "version": "1.0.0"
        },
        "paths": {
            "/test": {
                "get": {
                    "responses": {
                        "200": {
                            "description": "Success"
                        }
                    }
                }
            }
        },
        "x-tyk-api-gateway": {
            "info": {
                "name": "test-api-4.0",
                "state": {
                    "active": true
                }
            },
            "upstream": {
                "url": "http://localhost:8080"
            },
            "server": {
                "listenPath": {
                    "value": "/test-api-4.0/"
                }
            }
        }
    }`)

    t.Run("valid OAS 4.0 document with version 4.0.0", func(t *testing.T) {
        t.Parallel()
        err := ValidateOASObject(validOAS40Doc, "4.0.0")
        assert.NoError(t, err)
    })

    t.Run("valid OAS 4.0 document with version 4.0", func(t *testing.T) {
        t.Parallel()
        err := ValidateOASObject(validOAS40Doc, "4.0")
        assert.NoError(t, err)
    })
}

4.3 Add Template Validation Test

go
func TestValidateOASTemplate_4_0(t *testing.T) {
    t.Parallel()

    // Minimal OAS 4.0 template (missing required x-tyk fields)
    template40 := []byte(`{
        "openapi": "4.0.0",
        "info": {
            "title": "Template API 4.0",
            "version": "1.0.0"
        },
        "paths": {},
        "x-tyk-api-gateway": {}
    }`)

    t.Run("valid OAS 4.0 template", func(t *testing.T) {
        t.Parallel()
        err := ValidateOASTemplate(template40, "4.0")
        assert.NoError(t, err)
    })
}

4.4 Add Schema Retrieval Test

Update TestGetOASSchema with new test cases:

go
t.Run("return 4.0 schema when version 4.0 is requested", func(t *testing.T) {
    schema, err := GetOASSchema("4.0")
    assert.NoError(t, err)
    assert.NotEmpty(t, schema)
    
    // Verify it's the 4.0 schema by checking the definitions key
    defsKey := GetDefinitionsKey(schema)
    assert.Equal(t, "expected-key", defsKey, "OAS 4.0 should use 'expected-key'")
})

t.Run("return 4.0 schema when version 4.0.0 is requested", func(t *testing.T) {
    schema, err := GetOASSchema("4.0.0")
    assert.NoError(t, err)
    assert.NotEmpty(t, schema)
    
    defsKey := GetDefinitionsKey(schema)
    assert.Equal(t, "expected-key", defsKey)
})

Step 5: Run Tests

Run all tests to ensure the new version works correctly:

bash
# Run all OAS package tests
cd apidef/oas
go test -v

# Run specific tests
go test -v -run "Test_loadOASSchema|TestValidateOASObject_4_0|TestGetOASSchema"

Step 6: Update Default Version (Optional)

The default OAS version is controlled in the setDefaultVersion() function in validator.go.

To keep the current default version:

  • ✅ No changes needed. The code will automatically prefer the older version for stability.

To make the new version the default:

  • Update or remove the override logic in setDefaultVersion():
go
func setDefaultVersion() {
    var versions []string
    for k := range oasJSONSchemas {
        versions = append(versions, k)
    }

    latestVersion := findDefaultVersion(versions)

    // Remove or update this override when ready to use newer version
    if latestVersion == "4.0" {
        defaultVersion = "3.0"  // Keep 3.0 as default for now
    } else {
        defaultVersion = latestVersion
    }
}

Step 7: Document Breaking Changes

If the new OAS version has breaking changes or requires updates to existing code:

  1. Document the changes in the main project CHANGELOG
  2. Add migration notes for users upgrading to the new version
  3. Update examples and documentation to use the new version (if applicable)

Example: Adding OAS 3.1 Support

Here's what was done to add OAS 3.1 support:

  1. ✅ Added schema/3.1.json file
  2. ✅ Identified that OAS 3.1 uses $defs instead of definitions
  3. ✅ Updated GetDefinitionsKey() to detect $defs
  4. ✅ Updated loadOASSchema() to use detected key
  5. ✅ Updated ValidateOASTemplate() to use detected key
  6. ✅ Added comprehensive tests:
    • TestGetDefinitionsKey - Tests the helper function
    • TestValidateOASObject_3_1 - Tests validation of 3.1 documents
    • TestValidateOASTemplate_3_1 - Tests template validation
    • Updated Test_loadOASSchema - Verifies both 3.0 and 3.1
    • Updated TestGetOASSchema - Added 3.1 retrieval tests
  7. ✅ Kept default version at 3.0 for stability
  8. ✅ All tests pass ✅

Key Architecture Decisions

Automatic Schema Detection

The GetDefinitionsKey() function automatically detects which key a schema uses, making the code robust across versions.

Public API

GetDefinitionsKey() is a public function, allowing other products to leverage this detection logic.

Version Management

  • Schemas are stored by minor version (e.g., 3.0, 3.1)
  • Patch versions (e.g., 3.1.2) map to their minor version
  • Default version can be overridden for stability during transitions

Schema Injection

X-Tyk extensions are injected from schema/x-tyk-api-gateway.json into each OAS schema during load time, ensuring all schemas have consistent Tyk-specific validation.

Troubleshooting

Schema Not Loading

Problem: New schema file is not being loaded.

Solutions:

  • Ensure filename follows the pattern {major}.{minor}.json
  • Verify the file is valid JSON
  • Check that the file is in the schema/ directory
  • Ensure the file is included in the embed directive at the top of validator.go

X-Tyk Extensions Not Working

Problem: X-Tyk extensions are not being validated.

Solutions:

  • Check that GetDefinitionsKey() correctly detects the schema's definitions key
  • Verify that x-tyk-api-gateway.json uses the correct definitions key
  • Add debug logging to loadOASSchema() to see which key is being used

Tests Failing

Problem: Existing tests fail after adding new schema.

Solutions:

  • Check if Test_setDefaultVersion needs updating (it may expect a specific default)
  • Verify that the new schema doesn't conflict with existing schemas
  • Run tests in verbose mode to see detailed error messages: go test -v

References

Contributing

When adding a new OAS version:

  1. Follow this guide
  2. Ensure all tests pass
  3. Update this README if you discover any missing steps
  4. Submit a PR with clear description of changes

Overview (`ci/images/README.md`)

plugin-compiler

Does not support go modules. If your plugin has vendored modules that
are also used by tyk
gateway

then your module will be overridden by the version that Tyk uses.

Since 3.2, tyk has started using go.mod and thus your vendor'd code will no longer be overridden by tyk's versions.

Using the image

Assuming that you are in the plugin source directory and that you want to build a plugin for v3.0.4 of the the gateway,

shell
% docker run --rm -v `pwd`:/plugin-source tykio/tyk-plugin-compiler:v3.0.4 testplugin.so

You will find a testplugin.so in the current directory which is the file that goes into the API definition

Testing the image

shell
% export tag=v2.9.5
% rm -v testplugin/*.so
% docker run --rm -v `pwd`/testplugin:/plugin-source tykio/tyk-plugin-compiler:${tag} testplugin.so
% docker-compose -f test.yml up
....

Look for msg="API Loaded" api_id= api_name="Goplugin test" in the output. Test that the plugin is working correctly by,

shell
% curl http://localhost:8080/goplugin/headers
{
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip", 
    "Foo": "Bar", 
    "Host": "httpbin.org", 
    "User-Agent": "curl/7.68.0", 
    "X-Amzn-Trace-Id": "Root=1-606f4317-18581ac0164b5496739a5b32"
  }
}

The Foo: Bar header indicates all is well. Can be tested with jq like:

shell
% curl http://localhost:8080/goplugin/headers | jq '.headers.Foo == "Bar"'
true

Building the image

This will build the image that will be used in the plugin build
step. This section is for only for informational purposes.

In the root of the repo:

shell
docker build --build-arg TYK_GW_TAG=v2.8.4 -t tykio/tyk-plugin-compiler:v2.8.4 -f images/plugin-compiler/Dockerfile .

TYK_GW_TAG can be any github ref.


Overview (`ci/smoke-tests/README.md`)

How to write a test

  • Create a sub-directory for your test
  • Implement a script test.sh in your directory which will run the test and signal failure via exit code
  • Document the test in this file (if needed)

Note

The tests under this directory (smoke-tests/) are smoke tests maintained by a squads
to test a very specific functionality during the build. Regular tests that should be part of the
ci process should go to ci/tests/

How it works

Plugin aliasing

  • compiles foobar-plugin/main.go & helloworld-plugin/main.go using the appropriate plugin-compiler
  • mounts api definitions in foobar-plugin and helloworld-plugin into apps/

Run it as ./test.sh <version>. Depends on <version> being available in Docker Hub. See plugin-compiler/test.sh.

The foobar plugin adds a header Foo: Bar to all requests.
The helloworld plugin adds a header Hello: World

The test loads 2 APIs using foobar plugin and 2 APIs using helloworld plugin, it thereby ensures
that plugin compiler can be used to compiler multiple plugins, also it ensures that same plugin can be used in
multiple APIs


Overview (`ci/tests/README.md`)

How to write a test

  • Create a sub-directory for your test
  • Implement a script test.sh in your directory
  • Document the test in this file (if needed)

When invoking test.sh, the first argument needs to be the release
version being tested, e.g. v5.2.0. If the test fails, it should exit
with a non-zero exit code.

How it works

Requirements

You need docker compose v2 installed to run most tests. Docker compose is
used to bring up an environment from the configured services in
docker-compose.yml files. Generally it's advisable to run this with the
latest docker and docker compose versions installed.

For some extra tests, you'll also need https://taskfile.dev ; whenever
you see a Taskfile.yml, invoking task would either run the default
action, or print the available commands.

Linting

The Taskfile.yml in this directory serves to ensure that the bash scripts
are linted for common issues. The only issue that's excluded is the
following:

text
In ./api-functionality/test.sh line 12:
setup $1
      ^-- SC2086 (info): Double quote to prevent globbing and word splitting.

Other issues may appear, but you should be aware of the above. It's
turned off mainly for readability purposes, as we control the scripts
inputs and are not likely to cause harm from a test suite.

To run the linter, invoke task lint.

Run tests offline

To run tests offline, it's done with task run. To pass the gateway
release to use, run it as task tag=v5.2.0 run, or temporarily modify
the default version in the Taskfile. This is a generic test runner, if
you need to test against particular local docker images, read the
individual test suite details below.

Test suites

Plugin compiler

  • compiles testplugin/main.go using the appropriate plugin-compiler
  • mounts testplugin/apidef.json into apps/

Run it as ./test.sh <version>. Depends on <version> being available in Docker Hub. See plugin-compiler/test.sh.

It's possible to override the docker images used with:

  • GATEWAY_IMAGE - if not provided, tykio/tyk-gateway:<version> is used.
  • PLUGIN_COMPILER_IMAGE - if not provided, tykio/tyk-plugin-compiler:<version> is used.

The plugin adds a header Foo: Bar to all requests.

Python plugins

The bundler service serves two purposes:

  • compiles src/middleware.py using src/manifest.json using tyk bundle. This is done during the build phase.
  • serves bundle.zip from tyk bundle for the gw service

The plugin adds a header Foo: Bar to all requests.

Run it as ./test.sh <version>. Depends on <version> being available in Docker Hub. See python-plugins/test.sh.

It's possible to override the docker image used with:

  • GATEWAY_IMAGE - if not provided, tykio/tyk-gateway:<version> is used.

Basic Functionality testing

The test.sh script sets up the tyk-gateway with the <version>
provided. It sets the gateway up using docker-compose and includes a
very basic api endpoint that proxies requests to
http://httpbin.org/get.

The corresponding test passes an argument through to this endpoint, and
verifies whether it is returned properly thereby confirming that the
binary can actually start and run a basic api endpoint.

The file api_test.sh implements the actual test bit, test.sh invokes
the api_test.sh to execute the test.

We also have a file pkg_test.sh which tests the debian/rpm packages
from the release workflow, which also ultimately invokes the
api_test.sh.

It's possible to override the docker images used with:

  • GATEWAY_IMAGE - if not provided, tykio/tyk-gateway:<version> is used.

Overview (`coprocess/grpc/README.md`)

Coprocess (gRPC)

This feature makes it possible to write Tyk middleware using a gRPC backend. gRPC is a very interesting framework that has official support for many languages: C++, Java, Python, Go, Ruby, C#, Node.JS, Java, Objective C & PHP.

The Tyk Coprocess feature uses Protocol Buffers for dispatching messages (mostly requests and events), this makes it easier to connect Tyk to a gRPC server (which is based on Protocol Buffers too), it works by specifying a gRPC service definition that covers the dispatcher logic:

text
service Dispatcher {
  rpc Dispatch (Object) returns (Object) {}
  rpc DispatchEvent (Event) returns (EventReply) {}
}

gRPC backend

A very simple use case is as follows: you write a gRPC server in a language of your choice, using the Tyk's Protocol Buffer definitions.

When Tyk starts it performs a connection to your gRPC server (this is specified as a global setting in tyk.conf, you could use a UNIX socket -local- or even a TCP connection -over the network-).

When Tyk receives a request, it performs a call to your gRPC server, which is responsible for doing the actual tasks (middleware tasks like transformations or even authentication)

Global settings

This is a section of tyk.conf that will:

  • Enable the Coprocess feature.
  • Indicate your gRPC server address.
json
"coprocess_options": {
  "enable_coprocess": true,
  "coprocess_grpc_server": "tcp://127.0.0.1:5555"
},
"enable_bundle_downloader": true,
"bundle_base_url": "http://my-bundle-server.com/bundles/",
"public_key_path": "/path/to/my/pubkey",
  • enable_coprocess: Enables the rich plugins feature.
  • coprocess_grpc_server: Sets the gRPC server host address. This is only required for gRPC plugins.
  • enable_bundle_downloader: Enables the bundle downloader.
  • bundle_base_url: A base URL that will be used to download the bundle, in this example we have "test-bundle" specified in the API settings, Tyk will fetch the following URL: "http://my-bundle-server.com/bundles/test-bundle".
  • public_key_path: Sets a public key, this is used for verifying signed bundles, you may omit this if unsigned bundles are used.

API settings

This is a sample configuration that will authenticate your API through a Coprocess (gRPC in this case, see driver), and a hook a "pre" middleware.

json
"enable_coprocess_auth": true,
"custom_middleware": {
  "pre": [
    {
      "name": "MyPreMiddleware",
      "require_session": false
    }
  ],
  "auth_check": {
    "name": "MyAuthCheck"
  },
  "driver": "grpc"
},

Examples (Ruby)

You may find a Ruby sample here.


Overview (`coprocess/lua/README.md`)

Coprocess (Lua)

This feature makes it possible to write Tyk middleware using Lua.

The requirement is to have lua-cjson installed, an easier way of getting it is by using luarocks:

text
% luarocks install lua-cjson

Overview (`coprocess/python/README.md`)

Coprocess (Python)

This feature makes it possible to write Tyk middleware using Python, the current binding supports Python 3.x.
The purpose of this README is to provide an overview of the architecture and a few implementation notes.

Usage

You'll need to build Tyk with specific build tags, see build notes below.

Basically go build -tags 'coprocess python'.

Setting up custom Python middleware

The custom middleware should be specified in your API definition file, under custom_middleware (see coprocess_app_sample.json):

json
"custom_middleware": {
  "pre": [
      {
        "name": "MyPreMiddleware",
        "require_session": false
      }
    ],
  "post": [
    {
        "name": "MyPostMiddleware",
        "require_session": false
    }
  ],
  "driver": "python"
}

You can chain multiple hook functions when the hook type is Pre, Post or PostAuthCheck.

Tyk will load all the modules inside middleware/python.

The "name" field represents the name of a Python function, a sample Python middleware matching the sample definition above will look like (see middleware/python):

python
from tyk.decorators import *

@Pre
def MyPreMiddleware(request, session, spec):
    print("my_middleware: MyPreMiddleware")
    return request, session

@Post
def MyPreMiddleware(request, session, spec):
    print("my_middleware: MyPreMiddleware")
    return request, session

Authenticating an API with Python

See example: https://tyk.io/docs/plugins/supported-languages/rich-plugins/python/custom-auth-python-tutorial/

Writing events handlers with Python

It's also possible to write a Tyk event listener with Python. The first step is to set a custom event handler inside your API definition (see coprocess_app_sample_protected.json):

json
...
"event_handlers": {
  "events": {
    "AuthFailure": [
      {
        "handler_name": "cp_dynamic_handler",
        "handler_meta": {
          "name": "my_handler"
        }
      }
    ]
  }
},
...

In the above sample we're setting an event handler for AuthFailure events, an event that's triggered everytime a failed authentication occurs.

The handler_name must be cp_dynamic_handler.

The name field inside handler_meta refers to a Python function name that can be written inside event_handlers (see event_handlers/my_handler.py):

python
from tyk.decorators import Event

@Event
def my_handler(event, spec):
    print("-- my_handler:")
    print(" Event:", event)
    print(" Spec:", spec)

This function will be called when the specified event occurs, Tyk will pass a Python object like this:

json
{  
   "TimeStamp": "2016-08-19 11:13:31.537047694 -0400 PYT",
   "Meta":{  
      "Path":"/coprocess-auth-tyk-api-test/",
      "Origin":"127.0.0.1",
      "Message":"Auth Failure",
      "OriginatingRequest":"R0VUIC9jb3Byb2Nlc3MtYXV0aC10eWstYXBpLXRlc3QvIEhUVFAvMS4xDQpIb3N0OiAxMjcuMC4wLjE6ODA4MA0KVXNlci1BZ2VudDogY3VybC83LjQzLjANCkFjY2VwdDogKi8qDQpBdXRob3JpemF0aW9uOiAxDQoNCg==",
      "Key":""
   },
   "Type": "AuthFailure"
}

The above handler can be tested by sending a HTTP request to the protected Coprocess API, with an invalid authorization header:

text
curl http://127.0.0.1:8080/coprocess-auth-tyk-api-test/ -H 'Authorization: invalidtoken'

Build requirements

  • Python 3.x
  • Go
  • Cython (required if you need to modify and re-compile the gateway API binding)
  • protobuf (Python module): pip3 install protobuf==3.20.2
  • grpc (gRPC module): pip3 install grpcio

Build steps

To build Tyk with the Coprocess + Python support, use:

text
go build -tags 'coprocess python'

To compile the gateway API binding (assuming you're on the repository root):

sh
cd coprocess/python
./cythonize gateway

This will "cythonize" gateway.pyx, generating gateway.c and gateway.h.

To compile some other binding (where mybinding.pyx is your Cython input file):

sh
cd coprocess/python
./cythonize mybinding

cythonize is a helper script for compiling Python source files with Cython and patching the resulting source with the specific build tags used by this Coprocess feature.
This is important in order to keep Tyk build-able when a standard build is needed (and make the Go compiler ignore C binding files based on the specified build tags).

The top of a standard Cython binding file will look like this:

text
/* Generated by Cython 0.24.1 */

/* BEGIN: Cython Metadata
{
    "distutils": {
        "depends": []
    },
    "module_name": "gateway"
}
END: Cython Metadata */

After running cythonize the binding will have the correct build tags, and will be ignored if you don't build Tyk with these (go build -tags 'coprocess python'):

text
// +build coprocess
// +build python

/* Generated by Cython 0.24.1 */

/* BEGIN: Cython Metadata
{
    "distutils": {
        "depends": []
    },
    "module_name": "gateway"
}
END: Cython Metadata */

After re-compiling a binding, the C source code will change and you may want to build Tyk again.

CPython

Python has a very popular and well-documented C API, this coprocess feature makes a heavy use of it.

Built-in modules

All the standard Python modules are available and it's also possible to load additional ones, if you add them to your local Python installation (for example, using pip).

Coprocess Gateway API

There's a Python binding for the Coprocess Gateway API, this is written using the Cython syntax, it's basically a single file: gateway.pyx.

This binding exposes some functions, like a storage handler that allows you to get/set Redis keys:

python
from tyk.decorators import *
from gateway import TykGateway as tyk

@Pre
def SetKeyOnRequest(request, session, spec):
    tyk.store_data( "my_key", "expiring_soon", 15 )
    val = tyk.get_data("cool_key")
    return request, session

Cython bindings

Cython takes a .pyx file and generates a C source file (.c) with its corresponding header (.h), after this process, we use these two files as part of the cgo build process. This approach has been used as an alternative to cffi, which introduced an additional step into the setup, requiring the user to install the module first.

So in practice, we don't use the .pyx files directly (and they aren't required at runtime!). When the build process is over, the bindings are part of the Tyk binary and can be loaded and accessed from Go code.

The bindings declare an initialization function that should be called after the Python interpreter is invoked, this will load the actual module and make it possible to import it using import mymodule. This is how gateway.pyx and its functions become available.

Middleware and wrappers

There are quick wrappers for the HTTP request and session objects, written in Python, the idea is to provide an idiomatic way of writing middleware:

python
from tyk.decorators import *

@Pre
def AppendHeader(request, session, spec):
    request.add_header("custom_header", "custom_value")
    return request, session

The decorators provide a simple way of indicating when it's the right moment to execute your handlers, a handler that is decorated with Pre will be called before any authentication occurs, Post will occur after authentication and will have access to the session object.
You may find more information about Tyk middleware here.


Overview (`tests/regression/README.md`)

Regression tests

This is an area for tests that replicate demonstratable issues.

Any test should follow the following convention:

  • The name of the test file: issue_<jira-id>_test.go
  • The name of the test function: Test_Issue<jira-id>
  • Any test data required for the test: testdata/issue-<jira-id>-<type>.<ext>
  • Additional files: regression_test.go - containing shared utilities for tests

Overview (`tests/system/README.md`)

System tests

These tests are system level and depend on the current code structure
and go toolchain behaviour. They are flaky as code and the go toolchain
changes and drifts from what's asserted here.


Overview (`.vale/styles/write-good/README.md`)

Based on write-good.

Naive linter for English prose for developers who can't write good and wanna learn to do other stuff good too.

text
The MIT License (MIT)

Copyright (c) 2014 Brian Ford

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Overview (`ci/images/hybrid/README.md`)

Usage

Please refer to the official tyk.io docs for information about how to run this Multi-Cloud docker image:
Tyk Docs - Multi Cloud Usage

What's different about this Image and the Gateway Image?

The main differences between the Docker Hybrid image and the Docker Gateway Image are the following:

Includes Redis Server

This Hybrid image runs with an integrated Redis Server. You can see the dependency for the service installed in the Dockerfile, and then you can see the service being started in the entrypoint.sh file.

This is not optional. Should you choose to run your own Redis configuration, you will need to run the Gateway Image instead, and tweak the tyk.conf to run in multi-cloud.

Includes Nginx Server

This Hybrid image runs with an included Nginx server.

In order to disable it, just add an environment variable
-e DISABLENGINX=1
If you are running multi-cloud through the start.sh script, you will need to modify it there.

Tyk.conf

Of course, the tyk.conf included in this repo will already be setup to connect as a slave node to the MDCB( Multi Data Center Bridge) instance. You shouldn't need to make any changes to this file.


Overview (`ci/tests/error-overrides/README.md`)

Error Overrides Tests

This directory contains CI tests for the error_overrides feature.

Overview

Tests the error override functionality that allows customizing error responses.

Structure

text
error-overrides/
├── apps/               # API definitions for testing
├── configs/            # Gateway configuration files
│   ├── tyk.conf       # Gateway config with error_overrides enabled
│   └── nginx-ok.conf  # Backend nginx configuration
├── policies/           # Policy definitions
├── scripts/            # Test execution scripts
│   └── run-override-tests.sh
├── docker-compose.yml  # Test environment setup
├── test.sh            # CI entrypoint
└── README.md          # This file

Running Tests

In CI

The test.sh script is automatically discovered and run by the ci-tests job in .github/workflows/release-tests.yml.

bash
# CI sets GATEWAY_IMAGE environment variable
export GATEWAY_IMAGE=<ecr-registry>/tyk:sha-<commit>
./test.sh

Locally

bash
cd ci/tests/error-overrides

# Use a specific gateway image
export TYK_GATEWAY_IMAGE=tykio/tyk-gateway:v5.3
./test.sh

# Or use default
./test.sh

Test Coverage

The override tests verify error overrides for error classifications flag match:

  • 4xx Errors: AMF, AKI, RLT, QEX, BTL, CLM, BIV, IHD, TKI, TKE, EAD
  • 5xx Errors: TLE, TLI, TLM, TLN, TLH, TLP, UCF, URT, URR, UPE, DNS, NRH
  • Other: CBO, NHU, CDC

Each test verifies:

  1. Response body contains expected override content
  2. Response headers include custom error flags
  3. Response status code is overridden correctly
  4. Access logs contain the correct response_flag

Configuration

The error_overrides configuration in tyk.conf maps status codes and flags to custom responses:

json
{
  "error_overrides": {
    "429": [
      {
        "match": {"flag": "RLT"},
        "response": {
          "status_code": 429,
          "body": "{\"error\": \"rate_limit_exceeded\", ...}",
          "headers": {
            "X-Error-Flag": "RLT",
            "Retry-After": "60"
          }
        }
      }
    ]
  }
}

Environment Variables

  • TYK_GATEWAY_IMAGE - Gateway Docker image to test (default: tykio/tyk-gateway:latest)
  • GATEWAY_IMAGE - Alternative name for gateway image (used in CI)

Overview (`ci/tests/plugin-compiler/README.md`)

Plugin compiler tests

This is the test suite for the plugin compiler. Plugin compatibility
requirements are very strict. As documented in the
plugin package, particularly:

Applications that use plugins may require careful configuration to
ensure that the various parts of the program be made available in the
correct location in the file system (or container image). By contrast,
deploying an application consisting of a single static executable is
straightforward.

Runtime crashes are likely to occur unless all parts of the program
(the application and all its plugins) are compiled using exactly the same
version of the toolchain, the same build tags, and the same values of
certain flags and environment variables.

Similar crashing problems are likely to arise unless all common
dependencies of the application and its plugins are built from exactly
the same source code.

These are only some of the restrictions around plugins.

Taskfile

The test suite is set up using https://taskfile.dev.

The following variables are set as defaults:

Variable name Default value
tag v0.0.0
base tykio/golang-cross:1.22-bullseye
dockerfile ci/images/plugin-compiler/Dockerfile
image internal/plugin-compiler
sha $(git rev-parse HEAD)
root $(git rev-parse --show-toplevel)

Use task -l to list available targets, or read on.

Example: Run a plugin subtest against a release image.

text
task test:qa-plugin image=tykio/tyk-plugin-compiler:v5.3.9-rc4

Building and testing plugin compiler locally

In order to build the plugin compiler images locally from source,
you must issue task build in this folder.

To override the base image, you would issue:

text
task base=golang:1.20

The default action runs build and test targets.

We can test the following steps:

  • building plugin compiler locally
  • plugin compilation
  • plugin loading (tyk load -f -s)

Additionally, running the smoke test against a tag is possible.

Example:

text
./test.sh v5.2.1

The test takes optional GATEWAY_IMAGE and PLUGIN_COMPILER_IMAGE from
the environment, enabling testing on images that are not published to
docker hub. This functionality is used in the CI, to run smoke tests on
the docker images built from the release pipeline.


Overview (`ci/tests/specs/README.md`)

OpenAPI Specification Contract Testing with Portman

To ensure that the schema in our gateway's OpenAPI specification matches our API, we have implemented contract tests using Portman. Portman leverages Postman collections to automatically generate contract tests from our OpenAPI specifications.

What the Contract Tests Are Testing:

  1. Request Body Validation: Verifying that the request body defined in our Swagger schema matches the request body expected by our gateway API.

  2. Response Body Validation: Ensuring that the response returned by our gateway API matches the response body defined in our Swagger schema.

  3. Content-Type Validation: Confirming that the content types defined in the request and response schemas in our Swagger documentation match those expected and returned by our gateway API.

  4. Header Validation: Validating that the headers sent to or returned by the gateway API are as described in our Swagger documentation.

Overview of the Included Files

  1. config/portman-cli-options.json: This file sets the configuration for running the Portman CLI (similar to tyk.conf). It also specifies the location of your swagger.yml file. Full configuration options can be found here.

  2. config/portmanconfig.json: This file controls the content and how Portman generates the Postman tests. It specifies the types of tests to generate and any values to override in the generated tests. For example, to override headers or body content in the generated Postman tests, declare the overriding values in this file.

  3. package.json: Since Portman uses Node.js, this file specifies the Portman version to install and the command to run Portman.

  4. testdata/populate_gateway_test_data.yaml: A Venom test file that populates the gateway with data needed for the tests.

The following file is generated after running the tests:

  • gateway.collection.postman.json: Contains the tests generated by Portman that Newman will execute.

How to Run It Locally

To run the tests locally, install the following:

  1. Venom: Used to populate data needed for the Portman tests.

  2. Portman: Generates the contract tests from the Swagger file and converts them into Postman tests.

  3. Newman: A Postman CLI tool needed to run the contract tests.

Once these dependencies are installed, navigate to the ci/tests/specs directory and create a .env file using .env.example as template. Inside the .env file, add:

bash
PORTMAN_API_Key=<Your Tyk Gateway secret>

After adding the PORTMAN_API_Key, run the task command from ci/tests/specs directory to run a gateway instance and execute portman tests.

You can then stop the gateway instance by running task down command from the ci/tests/specs directory.

How It Is Run on the CI

The GitHub Action used to run these tests is swagger-contract-tests.yml.

  • In the CI environment, we launch a live gateway using the using th image created by release.yml GitHub action on every pull request.

  • After that, we run the task task tests, which uses Portman to generate and execute all the Swagger contract tests.


Overview (`ci/tests/tracing/README.md`)


title: Opentelemetry e2e Tests

Opentelemetry e2e Tests

This docker-compose file defines a multi-service application including the following components:

  • tyk: This service runs an internal Tyk gateway. So if you are running this from a cold start, make sure to execute make docker at the root of Tyk's repo. The environment variables for the tyk service are loaded from a local file, ./configs/tyk.env. It uses /apps folder to mount the preloaded APIs.

    • redis: This service runs another Redis server (version 4.0, based on Alpine Linux). The Redis server is configured to use an append-only file for data persistence.
  • otel-collector: This service runs an instance of the OpenTelemetry Collector (version 0.80.0). It is configured using the file /otel-local-config.yml, which is mounted from the local file ./tracetest/collector.config.yml.

  • tracetest: This service runs an application from the Docker image kubeshop/tracetest:v0.11.16. It depends on the otel-collector and postgres services. It uses a provisioning file located at /configs/tracetest/tracetest-provision.yml and a configuration file located at /configs/tracetest/tracetest.yml.
    It needs the following services to work:

    • postgres: This service runs a PostgreSQL database (version 14). It uses environment variables to set the PostgreSQL user and password. The health of the PostgreSQL service is checked by running the pg_isready command, which checks the connection to the database server.
    • queue: This service runs a RabbitMQ message broker, with the management plugin enabled. It checks the health of the RabbitMQ server using the rabbitmq-diagnostics check_running command.
    • cache: This service runs a Redis server (version 6) as a cache, with the unless-stopped restart policy. It periodically checks the health of the Redis server using the redis-cli ping command.

The tests folder contains all the *.yml declarations of tests that are going to be executed. Please take into consideration:

  • the target URL for gateway APIs is http://httpbin.org/ for HTTP APIs

Overview (`tests/streams/scripts/README.md`)

Scripts

Load Generators

This package contains multiple load generators for different protocols.

You can run them by using the command: ./load_gen <type>

AMQP

AMQP load generator, intended for testing purposes. Publishes messages to a RabbitMQ queue using the specified protocol.

text
$ ./load_gen amqp -h
Usage: ./load_gen amqp [options]

AMQP load generator. Publishes messages to a RabbitMQ queue using the specified protocol.

Options:
  -h, --help     Print this message and exit.
      --protocol AMQP protocol version to use. Supported values: amqp_0_9, amqp_1. Default: amqp_0_9.
      --url      RabbitMQ server URL. Default: amqp://guest:guest@localhost:5672/.
      --queue    RabbitMQ queue name. Default: tyk-streams-test-queue.
      --exchange RabbitMQ exchange name, only valid for amqp_0_9 Default: tyk-streams-test-exchange.

Sample usage:

text
$ ./load_gen amqp --protocol amqp_1
Publishing message to amqp_1: {payload: 1744619969422}
Publishing message to amqp_1: {payload: 1744619970429}
Publishing message to amqp_1: {payload: 1744619971436}
Publishing message to amqp_1: {payload: 1744619972439}
Publishing message to amqp_1: {payload: 1744619973450}
Publishing message to amqp_1: {payload: 1744619974460}
  • Queue name is tyk-streams-test-queue by default.
  • Exchange name is tyk-streams-test-exchange by default. It's only required for amqp_0_9.

MQTT

MQTT load generator, intended for testing purposes. Publishes messages to an MQTT broker on the specified topic.

text
$ ./load_gen mqtt -h
Usage: ./load_gen mqtt [options]

MQTT load generator. Publishes messages to an MQTT broker on the specified topic.

Options:
  -h, --help     Print this message and exit.
      --broker   MQTT broker URL. Default: tcp://localhost:1883.
      --topic    MQTT topic to publish to. Default: tyk-streams-test-topic.
      --clientid MQTT client ID. Default: tyk-mqtt-load-generator.
      --qos      MQTT QoS level (0, 1, or 2). Default: 1.
      --username MQTT username (optional).
      --password MQTT password (optional).

Sample usage:

text
$ ./load_gen mqtt
2023/05/15 10:23:45 Connected to MQTT broker at tcp://localhost:1883
2023/05/15 10:23:46 Publishing message to MQTT topic 'tyk-streams-test-topic': {"payload":1684142626000}
2023/05/15 10:23:47 Publishing message to MQTT topic 'tyk-streams-test-topic': {"payload":1684142627000}
2023/05/15 10:23:48 Publishing message to MQTT topic 'tyk-streams-test-topic': {"payload":1684142628000}
2023/05/15 10:23:49 Publishing message to MQTT topic 'tyk-streams-test-topic': {"payload":1684142629000}
  • Topic name is tyk-streams-test-topic by default.
  • QoS level is 1 (at least once delivery) by default.
  • The generator publishes messages at 1-second intervals.

WebSocket

WebSocket load generator, intended for testing purposes. Sends location data to a WebSocket server at the specified URL.

text
$ ./load_gen ws -h
Usage: ./load_gen ws [options]

WebSocket load generator. Sends location data to a WebSocket server at the specified URL.

Options:
  -h, --help     Print this message and exit.
      --url      WebSocket server URL. Default: ws://localhost:8080/ws.

Sample usage:

text
$ ./load_gen ws
2023/05/15 10:23:45 Connected to WebSocket server at ws://localhost:8080/ws
2023/05/15 10:23:46 Sending message to WebSocket server: {"locations":[{"name":"Berlin","country":"Germany"},{"name":"London","country":"UK"},{"name":"Rhodes","country":"Greece"},{"name":"Washington D.C.","country":"USA"},{"name":"Athens","country":"Greece"}]}
2023/05/15 10:23:47 Sending message to WebSocket server: {"locations":[{"name":"Berlin","country":"Germany"},{"name":"London","country":"UK"},{"name":"Rhodes","country":"Greece"},{"name":"Washington D.C.","country":"USA"},{"name":"Athens","country":"Greece"}]}
2023/05/15 10:23:48 Sending message to WebSocket server: {"locations":[{"name":"Berlin","country":"Germany"},{"name":"London","country":"UK"},{"name":"Rhodes","country":"Greece"},{"name":"Washington D.C.","country":"USA"},{"name":"Athens","country":"Greece"}]}
  • The default WebSocket URL is ws://localhost:8080/ws.
  • The generator sends a fixed JSON payload with location data.
  • The generator sends messages at 1-second intervals.

Broker

RabbitMQ (AMQP / MQTT)

To run RabbitMQ 4.0.x on your local, you can run it in a Docker container:

shell
docker run -it --rm --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:4.0-management

The management UI can be accessed using a Web browser at http://localhost:15672/

You may need to enable rabbitmq_management plugin, but it's generally enabled by default. If it's not, you should
check out the documentation: https://www.rabbitmq.com/docs/management#getting-started

Docker Compose

If you want to have 3 distinct RabbitMQ instances with AMQP 0-9-1, AMQP 1.0 and MQTT, you can use following docker-compose.yml:

yml
version: "3.8"

services:
  rabbitmq-amqp-0-9-1:
    image: rabbitmq:3-management
    container_name: rabbitmq_amqp_0_9_1
    ports:
      - "15672:15672" # Management UI
      - "5672:5672" # AMQP port
    environment:
      RABBITMQ_DEFAULT_USER: guest
      RABBITMQ_DEFAULT_PASS: guest
    networks:
      - rabbitmq_network

  rabbitmq-amqp-1-0:
    image: rabbitmq:3-management
    container_name: rabbitmq_amqp_1_0
    ports:
      - "15673:15672" # Management UI
      - "5673:5672" # AMQP port
    environment:
      RABBITMQ_DEFAULT_USER: guest
      RABBITMQ_DEFAULT_PASS: guest
      RABBITMQ_ENABLED_PLUGINS_FILE: /etc/rabbitmq/custom_plugins.conf
    volumes:
      - "./rabbitmq_amqp_1_0_plugins.conf:/etc/rabbitmq/custom_plugins.conf"
    networks:
      - rabbitmq_network

  rabbitmq-mqtt:
    image: rabbitmq:3-management
    container_name: rabbitmq_mqtt
    ports:
      - "15674:15672" # Management UI
      - "5674:5672" # AMQP
      - "1883:1883" # MQTT
    environment:
      RABBITMQ_DEFAULT_USER: guest
      RABBITMQ_DEFAULT_PASS: guest
    networks:
      - rabbitmq_network
    command: >
      bash -c "
        rabbitmq-plugins enable --offline rabbitmq_mqtt;
        rabbitmq-server
      "

networks:
  rabbitmq_network:
    driver: bridge

In that case, the AMQP 1.0 instance needs an additional file alongside called rabbitmq_amqp_1_0_plugins.conf.
This should have the following contents:

text
[rabbitmq_management,rabbitmq_amqp1_0].

Mosquitto MQTT Broker

To run an MQTT broker locally for testing, you can use Eclipse Mosquitto in a Docker container:

shell
docker run -it --rm --name mosquitto -p 1883:1883 -p 9001:9001 eclipse-mosquitto:2.0

For a more complete setup with authentication and persistence, you can create a custom configuration file and mount it:

shell
docker run -it --rm --name mosquitto -p 1883:1883 -p 9001:9001 \
  -v $(pwd)/mosquitto.conf:/mosquitto/config/mosquitto.conf \
  -v $(pwd)/data:/mosquitto/data \
  -v $(pwd)/log:/mosquitto/log \
  eclipse-mosquitto:2.0

Further reading

AMQP

https://www.rabbitmq.com/tutorials/amqp-concepts
https://www.rabbitmq.com/docs/publishers
https://www.rabbitmq.com/tutorials/tutorial-one-go
https://www.rabbitmq.com/docs/next/amqp
https://www.cloudamqp.com/blog/part1-rabbitmq-for-beginners-what-is-rabbitmq.html

MQTT

https://mqtt.org/
https://www.hivemq.com/mqtt-essentials/
https://mosquitto.org/documentation/

WebSocket

https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API
https://github.com/gorilla/websocket


Overview (`ci/smoke-tests/plugin-aliasing/foobar-plugin/README.md`)

Add FooBar Header Plugin

This plugin adds a header Foo : Bar to every request


Overview (`ci/smoke-tests/plugin-aliasing/helloworld-plugin/README.md`)

Add HelloWorld Header Plugin

This plugin adds a header Hello : World to every request


Overview (`ci/tests/python-plugins/extend-python/README.md`)

Tyk Gateway with Python Coprocess

Starting from Tyk Gateway version v5.3.0, Python is no longer bundled with the official Docker images.
However, Tyk Gateway continues to support Python plugins functionality.

If you wish to use Python plugins, you can extend the official Docker image by adding Python to it.
This directory contains a sample Dockerfile that demonstrates how to achieve this.


Overview (`ci/tests/plugin-compiler/testdata/complex-plugin/README.md`)

Complex plugin

This location serves as a more complex plugin that has self-referencing
import paths. The import paths get replaced in plugin compiler.

It's a structural rewrite of ../basic-plugin and adds no new functionality.

For more details about the test case, run task in parent folder, or
cross reference with the implementation of plugin_id in plugin compiler.


Overview (`ci/tests/plugin-compiler/testdata/qa-plugin/README.md`)

QA Plugin

The only difference between this and other plugins is that this one does
not provide a go.mod. This means a slightly different build path is
tested for the plugin compiler, ensuring coverage for when no go.mod is
provided.


Overview (`ci/tests/plugin-compiler/testdata/test-plugin/README.md`)

OAuth2.0 Introspection Plugin for Tyk Gateway

This plugin allows the Tyk Gateway to perform an OAuth2.0 Introspection request
to a 3rd party authorization server in order to validate an access token which may be a JWT or opaque.

The plugin aims for compliance with RFC7662: OAuth 2.0 Token Introspection.

Configuration

Configuration is via environment variables.

.env
OAUTH2_INTROSPECT_ENDPOINT="https://SOME_AUTHORIZATION_SERVER_INTROSPECTION_ENDPOINT"
OAUTH2_INTROSPECT_AUTHORIZATION="AUTHORIZATION_HEADER_VALUE"

Basic Auth Example

text
OAUTH2_INTROSPECT_AUTHORIZATION="Bearer 23410913-abewfq.123483"

     POST /introspect HTTP/1.1
     Host: server.example.com
     Accept: application/json
     Content-Type: application/x-www-form-urlencoded
     Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW

     token=2YotnFZFEjr1zCsicMWpAA

Bearer token example

text
OAUTH2_INTROSPECT_AUTHORIZATION="Bearer 23410913-abewfq.123483"

     POST /introspect HTTP/1.1
     Host: server.example.com
     Accept: application/json
     Content-Type: application/x-www-form-urlencoded
     Authorization: Bearer 23410913-abewfq.123483

     token=2YotnFZFEjr1zCsicMWpAA

Usage

Assuming JWT authentication is enabled, Tyk can Authenticate using the access_token (sounds weird I know).

Ensure that the API definition does not strip the access_token, and configure this plugin in the post_key_auth hook.

json
{
  "custom_middleware": {
    "post_key_auth": [{
      "name": "OAuth2Introspect",
      "path": "SOMEDIRECTORY/oauth2_introspection/oauth2_introspection.so"
    }],
  "driver": "goplugin",
}

This plugin will then strip the token to ensure that it doesn't leak upstream.

If there is a scope object returned in the introspection response, this will be injected into the
X-Tyk-Plugin-Oauth2Introspect-Scope request header. It may be useful for the upstream service, or a middleware
further down the request chain.

Maybe TODO / Current Limitations

  • Build internal configurable cache to not spam the introspection endpoint.
  • Hardcoded to only introspect the access_token. We do not introspect the refresh_token.
  • Hardcoded to only look in the authorization header for the access_token
  • Pull in introspection configuration from the API Definition config_data object to make runtime dynamic.

Taskfile.Yml (`docs/Taskfile.yml`)


version: "3"

Running this taskfile ensures that markdowns are

consistently formatted according to mdox rules.

tasks:
default:
desc: "Run mdox to format markdowns"
cmds:
- mdox fmt --no-soft-wraps $(find -name '*.md')


Dev Apidef Oas (`docs/dev/apidef-oas.md`)

Translating Tyk OAS API Definition to Tyk Classic API Definition (and vice versa)

To ensure feature parity between Tyk OAS APIs and Tyk classic API definitions, follow these guidelines:

Define necessary Structs

Define the necessary structs or add the necessary fields in the apidef/oas package.

Make sure json and bson tags are added to the fields.

If an enabled flag is specified in the OAS contract, make sure a corresponding disabled or enabled flag is added in the classic API definition.

Also make sure that disabled/enabled flag toggles the feature on or off.

Why `disabled` or `enabled` in classic API definition?

Historically, almost every feature/middleware in Tyk is considered enabled by default when value for feature controls are non zero values. It is disabled when the feature controls are having zero values. For this reason, whenever an existing feature is migrated to OAS, and OAS has an enabled flag then a disabled flag is added to give explicit control to turn off the feature.

Please also make sure that the disabled flags are set to true in APIDefinition.SetDisabledFlags(), so that it is not enabled in OAS by default.

Use camelCase Notation

Ensure OAS fields follow camelCase notation for json and bson tags.

Handle Required Fields

For fields that are required:

  1. Do not use omitempty in struct tags.

  2. Do not use pointer types for required fields.

  3. Add a comment // required towards the end of a required field so that automation generates docs accordingly.

  4. As a convention, we also try to add the corresponding classic API definition fields in godoc in the following format

    text
    // Tyk classic API definition: `!use_keyless`.

    This might not be perfect at this moment, but we aim to keep this link so that customers find it easier to follow the docs.

Handle Optional Fields

For optional fields:

  1. Add the omitempty tag

  2. Use pointer types for structs.

  3. Make sure that omitempty tag is added for slice fields that are optional.

Add Go Doc Comments

Add comments in Go doc format for each field to enable automated documentation generation (this is validated by linter).

Implement Fill and ExtractTo Methods

Every OAS struct should follow the convention of having Fill(apidef.APIDefinition) and ExtractTo(*apidef.APIDefinition) methods:

Fill populates the struct from a classic API definition.

ExtractTo extracts the contents of an OAS API definition into a classic API definition.

Implement Fill Method Pattern

Each Fill method should follow this pattern:

go
if u.RateLimit == nil {
	u.RateLimit = &RateLimit{}
}

u.RateLimit.Fill(api)
if ShouldOmit(u.RateLimit) {
	u.RateLimit = nil
}

This ensures the field is reset to empty when not configured in the classic API definition.

Working with `VersionData`

A Main version will be provided that can be used for Fill.

go
api.VersionData.Versions[Main]

Implement ExtractTo Method Pattern

Similarly, follow this pattern with ExtractTo:

go
if u.RateLimit == nil {
	u.RateLimit = &RateLimit{}
	defer func() {
		u.RateLimit = nil
	}()
}

u.RateLimit.ExtractTo(api)

Working with `VersionData`

There are 2 helper functions for ExtractTo that will help to handle VersionData. You can use them like this:

go
func (g *GlobalRequestSizeLimit) ExtractTo(api *apidef.APIDefinition) {
	mainVersion := requireMainVersion(api)
	defer func() {
		updateMainVersion(api, mainVersion)
	}()

	// manipulate the Main VersionInfo here
}

Write Tests

Write tests for conversion functions. Refer to the example: https://github.com/TykTechnologies/tyk/pull/5979/files#diff-222cc254c0c6c09fa0cf50087860b837a0873e2aef3c84ec7d80b1014c149057R97

Update TestOAS_ExtractTo_ResetAPIDefinition

Maintain and update the list of fields that are not OAS compatible in the TestOAS_ExtractTo_ResetAPIDefinition test.

Update JSON Schema

Update the JSON schema for the x-tyk-api-gateway struct in: https://github.com/TykTechnologies/tyk/blob/master/apidef/oas/schema/x-tyk-api-gateway.json

Ensure this schema is updated whenever the OAS API definition is modified.


Dev How To Generate Bento Config Validation Schema (`docs/dev/how-to-generate-bento-config-validation-schema.md`)

How to generate Bento Configuration Validator Schema for Tyk

There is a script named generate_bento_config_schema under apidef/streams/bento/schema folder in Tyk GW repository.
This script generates a JSON schema for the input & output resources that Tyk supports. There are two ways to run this script.

Running the script directly

Simply, go run generate_bento_config_schema.go command in the source code folder.
It'll generate a file named bento-config-schema.json in the current working folder.
You can also set an output path via -output <string> parameter.

This is useful for development purposes.

Running via task runner

Simply run the following command under Tyk GW repository’s root folder.

shell
task generate-bento-config-validator-schema

The task will automatically update apidef/streams/bento/schema/bento-config-schema.json file.
This should be done after upgrading Bento in Tyk GW.

How to add a new input & output resources

1- Import the related component for its side effects.

For example if you want to produce a JSON schema that supports redis component,
you can import it like the following:

go
_ "http://github.com/warpstreamlabs/bento/public/components/redis"`

2- Add the component name to supportedSources slice. You should know that some
components exposes different input/output sources.

For example, components/kafka exposes kafka and kafka_franz. You need to dig into the Bento's codebase to understand
which input/output is exposed by a component.

See the list of components: https://github.com/warpstreamlabs/bento/tree/main/public/components

Importing all components was not preferred because it results in generating a gigantic JSON schema, and it’s too hard to
navigate in that file and debug possible issues.

How to add a new validator rule

A validator rule has been defined by a simple interface:

go
// customValidationRule is an interface for defining custom validation rules for JSON schemas.
type customValidationRule interface {
	// Name returns the name of the validation rule.
	Name() string

	// Apply applies the validation rule to the provided input data and
	// returns the processed data or an error if validation fails.
	Apply(input []byte) ([]byte, error)
}

Apply method takes the input and returns the processed input by the validator rule implementation.

You can add rules to the pipeline like the following, generateBentoConfigSchema will take a list of rules and apply them respectively.

go
err = generateBentoConfigSchema(args.output, []customValidationRule{
	&addURIFormatToHTTPClientRule{}
})

Dev Oas Fixtures (`docs/dev/oas-fixtures.md`)

OAS Fixtures

A test fixture is a device used to consistently test some item, device, or piece of software.

It's a common concern to answer the following questions:

  • What is the definition of an OAS feature like UptimeTests
  • What are the classic API fields that contract is being migrated to
  • Are classic settings migrated to OAS definitions correctly (reverse test)

The fixtures are located under apidef/oas/testdata/fixtures. Extend the location with new migration tests. Lets take a look at an example development flow.

In addition to running task in apidef/oas, you can use the fixtures test file to run only the fixtures, due to the black box nature of the test.

bash
go test -count=1 -v fixtures_test.go

The tests are run and filled from fixture yaml files.

Creating a fixture

When creating a fixture, create a plugins.yml or service_discovery.yml or similarly named files under apidef/oas fixtures. Start with either a classic API setting, or an OAS API setting, for example:

yaml
---
name: "Plugins"
tests:
  - desc: "From OAS to Classic"
    source: oas
    debug: true
    input:
      x-tyk-api-gateway:
        server:
          authentication:
            custom:
              enabled: true
              functionName: "name"
              path: "/path/to/file.so"
              rawBodyOnly: true
              requireSession: true
  • source can be oas or classic
  • debug set to true will print which fields have been modified
  • input declares api definition schema based on source
  • output checks the migrated api definition for values

This fixture defines a test case that sets OAS inputs. By running the fixture tests, they detect this and print any changed values vs. the migration that would result with an empty input. The idea behind it is that each input field maps to one or more values between schemas.

If you don't configure output and set debug=true:

text
=== RUN   TestFixtures/Plugins/From_OAS_to_Classic
    fixtures_test.go:280: Ignores: []
    fixtures_test.go:281: Changed keys after migration:
    fixtures_test.go:306: - custom_plugin_auth_enabled "true"

This gives you a way to fill out output.

yaml
output:
      custom_plugin_auth_enabled: true

For the migration example you can see that some fields have not been migrated (functionName, path, etc.). The list of detected results is untrimmed, assertions should be added and migration fixed.

Additional fixture settings

Additional fixture settings are possible for asserting errors:

yaml
errors:
      enabled: true
      want: true

By default error checks are not enabled, and if you enable them then it should be set to want: true if an error is expected. Due to validation failing with API definition partials, it's suggested to leave this inconfigured.

Debug output filtering

If you're looking at debug output and want to ignore keys or values, you can configure ignores to skip a combination of keys or values you set:

yaml
ignores:
      - key: "use_"
        values: [false]
      - key: "disabled"
        values: [true]
      - values: ["", 0]

Dev Oas Only Feature Development (`docs/dev/oas_only_feature_development.md`)

OAS-Only Feature Development Guide

Overview

This guide outlines the standard approach for developing new features that exclusively use OpenAPI Specification (OAS) in Tyk. This approach helps reduce technical debt by moving away from dual configurations (classic API definition and OAS) to a single source of truth using OAS.

Architecture

Core Components

  1. APISpec Structure
text
type APISpec struct {
    *apidef.APIDefinition
    OAS oas.OAS
    
    // Helper method for retrieving OAS features
    func (s *APISpec) GetTykExtension() *XTykAPIGateway {
      ...
    }
}
  1. Tyk Extension in OAS
text
type XTykAPIGateway struct {
    Info       Info       `json:"info"`
    Upstream   Upstream   `json:"upstream"`
    Server     Server     `json:"server"`
    Middleware *Middleware `json:"middleware,omitempty"`
}

Development Guidelines

1. Feature Configuration

  1. Add your feature configuration to the XTykAPIGateway structure:
text
type XTykAPIGateway struct {
    // Existing fields...
    Middleware *Middleware `json:"middleware,omitempty"`
    
    // New feature configuration
    MyNewFeature *MyNewFeatureConfig `json:"myNewFeature,omitempty"`
}

type MyNewFeatureConfig struct {
    Enabled bool `json:"enabled"`
    // Feature-specific configuration
}

2. Middleware Implementation

  1. Create a new middleware structure:
text
type MyFeatureMiddleware struct {
    BaseMiddleware
}

func NewMyFeatureMiddleware() *MyFeatureMiddleware {
    return &MyFeatureMiddleware{}
}
  1. Implement the ProcessRequest method:
text
func (m *MyFeatureMiddleware) ProcessRequest(w http.ResponseWriter, r *http.Request, conf interface{}) (error, int) {
    if !m.Spec.APIDefinition.IsOAS {
        // Feature only available for OAS APIs
        return nil, http.StatusOK
    }

    tykExt := m.Spec.GetTykExtension()
    if tykExt.MyNewFeature == nil || !tykExt.MyNewFeature.Enabled {
        return nil, http.StatusOK
    }

    // Implement feature logic here
    return m.handleFeature(w, r, tykExt.MyNewFeature)
}

3. Testing Strategy

Testing OAS-only features requires special attention to how the feature behaves with different API definition types. Focus on these key scenarios:

text
/* Detailed source-code truncated for AI context efficiency. */

The test cases above cover:

  1. Classic API definitions (should pass through)
  2. OAS APIs with nil or empty feature configuration
  3. OAS APIs with valid feature configuration

Each test verifies that the middleware:

  • Handles invalid states gracefully
  • Returns appropriate status codes
  • Doesn't panic on nil values
  • Processes valid configurations correctly

4. Documentation Requirements

  1. API Documentation

    • Document your feature in the Tyk API documentation
    • Include OpenAPI extension examples
    • Provide clear configuration options
  2. Code Documentation

    • Add godoc comments to all exported types and functions
    • Include examples in code documentation
    • Document any non-obvious behaviors or edge cases

Best Practices

  1. Configuration
    • Implement sensible defaults
    • Validate configurations early

2Testing

  • Achieve >80% code coverage
  • Test edge cases and error conditions
  • Include performance benchmarks for critical paths

Backward Compatibility

While OAS-only features don't need classic API definition support, ensure:

  1. Feature gracefully handles classic API definitions
  2. Clear documentation about OAS-only support
  3. Appropriate error messages for unsupported configurations

Release Considerations

1Migration

  • Provide migration guides if replacing existing functionality
  • Include validation tools for configuration
  • Document

Plugins Go Development Flow (`docs/plugins/go-development-flow.md`)


title: Custom Go plugin development flow
tags:
- custom plugin
- golang
- go plugin
- middleware
- debugging go plugins

date: "2024-10-11"

We recommend that you familiarize yourself with the following official Go documentation to help you work effectively with Go plugins:

{{< note success >}} Note

Plugins are currently supported only on Linux, FreeBSD, and macOS, making them unsuitable for applications intended to be portable. {{< /note >}}

Plugins need to be compiled to native shared object code, which can then be loaded by Tyk Gateway. It's important to understand the need for plugins to be compiled using exactly the same environment and [build flags]({{< ref "product-stack/tyk-gateway/advanced-configurations/plugins/golang/go-development-flow#build-flags" >}}) as the Gateway. To simplify this and minimise the risk of compatibility problems, we recommend the use of Go workspaces, to provide a consistent environment.

Setting up your environment

To develop plugins, you'll need:

  • Go (matching the version used in the Gateway, which you can determine using go.mod).
  • Git to check out Tyk Gateway source code.
  • A folder with the code that you want to build into plugins.

We recommend that you set up a Go workspace, which, at the end, is going to contain:

  • /tyk-release-x.y.z - the Tyk Gateway source code
  • /plugins - the plugins
  • /go.work - the Go workspace file
  • /go.work.sum - Go workspace package checksums

Using the Go workspace ensures build compatibility between the plugins and Gateway.

1. Checking out Tyk Gateway source code

text
git clone --branch release-5.3.6 https://github.com/TykTechnologies/tyk.git tyk-release-5.3.6 || true

This example uses a particular release-5.3.6 branch, to match Tyk Gateway release 5.3.6. With newer git versions, you may pass --branch v5.3.6 and it would use the tag. In case you want to use the tag it's also possible to navigate into the folder and issue git checkout tags/v5.3.6.

2. Preparing the Go workspace

Your Go workspace can be very simple:

  1. Create a .go file containing the code for your plugin.
  2. Create a go.mod file for the plugin.
  3. Ensure the correct Go version is in use.

As an example, we can use the CustomGoPlugin.go sample as the source for our plugin as shown:

text
mkdir -p plugins
cd plugins
go mod init testplugin
go mod edit -go $(go mod edit -json go.mod | jq -r .Go)
wget -q https://raw.githubusercontent.com/TykTechnologies/custom-go-plugin/refs/heads/master/go/src/CustomGoPlugin.go
cd -

The following snippet provides you with a way to get the exact Go version used by Gateway from it's go.mod file:

  • go mod edit -json go.mod | jq -r .Go (e.g. 1.22.7)

This should be used to ensure the version matches between gateway and the plugin.

To summarize what was done:

  1. We created a plugins folder and initialzed a go project using go mod command.
  2. Set the Go version of go.mod to match the one set in the Gateway.
  3. Initialzied the project with sample plugin go code.

At this point, we don't have a Go workspace but we will create one next so that we can effectively share the Gateway dependency across Go modules.

3. Creating the Go workspace

To set up the Go workspace, start in the directory that contains the Gateway and the Plugins folder. You'll first, create the go.work file to set up your Go workspace, and include the tyk-release-5.3.6 and plugins folders. Then, navigate to the plugins folder to fetch the Gateway dependency at the exact commit hash and run go mod tidy to ensure dependencies are up to date.

Follow these commands:

text
go work init ./tyk-release-5.3.6
go work use ./plugins
commit_hash=$(cd tyk-release-5.3.6 && git rev-parse HEAD)
cd plugins && go get github.com/TykTechnologies/tyk@${commit_hash} && go mod tidy && cd -

The following snippet provides you to get the commit hash exactly, so it can be used with go get.

  • git rev-parse HEAD

The Go workspace file (go.work) should look like this:

text
go 1.22.7

use (
	./plugins
	./tyk-release-5.3.6
)

4. Building and validating the plugin

Now that your Go workspace is ready, you can build your plugin as follows:

text
cd tyk-release-5.3.6 && go build -tags=goplugin -trimpath . && cd -
cd plugins           && go build -trimpath -buildmode=plugin . && cd -

These steps build both the Gateway and the plugin.

You can use the Gateway binary that you just built to test that your new plugin loads into the Gateway without having to configure and then make a request to an API using this command:

text
./tyk-release-5.3.6/tyk plugin load -f plugins/testplugin.so -s AuthCheck

You should see an output similar to:

text
time="Oct 14 13:39:55" level=info msg="--- Go custom plugin init success! ---- "
[file=plugins/testplugin.so, symbol=AuthCheck] loaded ok, got 0x76e1aeb52140

The log shows that the plugin has correctly loaded into the Gateway and that its init function has been successfully invoked.

5. Summary

In the preceding steps we have put together an end-to-end build environment for both the Gateway and the plugin. Bear in mind that runtime environments may have additional restrictions beyond Go version and build flags to which the plugin developer must pay attention.

Compatibility in general is a big concern when working with Go plugins: as the plugins are tightly coupled to the Gateway, consideration must always be made for the build restrictions enforced by environment and configuration options.

Continue with Loading Go Plugins into Tyk.

Debugging Golang Plugins

Plugins are native Go code compiled to a binary shared object file. The code may depend on cgo and require libraries like libc provided by the runtime environment. The following are some debugging steps for diagnosing issues arising from using plugins.

Warnings

The Plugin package - Warnings section in the Go documentation outlines several requirements which can't be ignored when working with plugins. The most important restriction is the following:

Runtime crashes are likely to occur unless all parts of the program (the application and all its plugins) are compiled using exactly the same version of the toolchain, the same build tags, and the same values of certain flags and environment variables.

We provide the Tyk Plugin Compiler docker image, which we strongly recommend is used to build plugins compatible with the official Gateway releases. This tool provides the cross compilation toolchain, Go version used to build the release, and ensures that compatible flags are used when compiling plugins, like -trimpath, CC, CGO_ENABLED, GOOS, GOARCH.

The Plugin Compiler also works around known Go issues such as:

Supplying the argument build_id to the Plugin Compiler ensures the same plugin can be rebuilt. The Plugin Compiler does this by replacing the plugin go.mod module path.

Continue with Tyk Plugin Compiler.

Using Incorrect Build Flags

When working with Go plugins, it's easy to miss the restriction that the plugin at the very least must be built with the same Go version, and the same flags (notably -trimpath) as the Tyk Gateway on which it is to be used.

If you miss an argument (for example -trimpath) when building the plugin, the Gateway will report an error when your API attempts to load the plugin, for example:

text
task: [test] cd tyk-release-5.3.6 && go build -tags=goplugin -trimpath .
task: [test] cd plugins && go build -buildmode=plugin .
task: [test] ./tyk-release-5.3.6/tyk plugin load -f plugins/testplugin.so -s AuthCheck
tyk: error: unexpected error: plugin.Open("plugins/testplugin"): plugin was built with a different version of package internal/goarch, try --help

Usually when the error hints at a standard library package, the build flags between the Gateway and plugin binaries don't match.

Other error messages may be reported, depending on what triggered the issue. For example, if you omitted -race in the plugin but the gateway was built with -race, the following error will be reported:

text
plugin was built with a different version of package runtime/internal/sys, try --help

Strictly speaking:

  • Build flags like -trimpath, -race need to match.
  • Go toolchain / build env needs to be exactly the same.
  • For cross compilation you must use the same CC value for the build (CGO).
  • CGO_ENABLED=1, GOOS, GOARCH must match with runtime.

When something is off, you can check what is different by using the go version -m command for the Gateway (go version -m tyk) and plugin (go version -m plugin.so). Inspecting and comparing the output of build tokens usually yields the difference that caused the compatibility issue.

Plugin Compatibility Issues

Below are some common situations where dependencies might cause issues:

  • The Gateway has a dependency without a go.mod file, but the plugin needs to use it.
  • Both the Gateway and the plugin share a dependency. In this case, the plugin must use the exact same version as the Gateway.
  • The plugin requires a different version of a shared dependency.

Here’s how to handle each case:

Case 1: Gateway dependency lacks go.mod

  • The plugin depends on the Gateway, which uses dependency A.
  • A doesn’t have a go.mod file, so a pseudo version is generated during the build.
  • Result: The build completes, but the plugin fails to load due to a version mismatch.

Solution: Update the code to remove dependency A, or use a version of A that includes a go.mod file.

Case 2: Shared dependency with version matching

  • The plugin and Gateway share a dependency, and this dependency includes a go.mod file.
  • The version matches, and the dependency is promoted to direct in go.mod.
  • Outcome: You’ll need to keep this dependency version in sync with the Gateway.

Case 3: Plugin requires a different version of a shared dependency

  • The plugin and Gateway share a dependency, but the plugin needs a different version.
  • If the other version is a major release (e.g., /v4), it’s treated as a separate package, allowing both versions to coexist.
  • If it’s just a minor/patch difference, the plugin will likely fail to load due to a version conflict.

Recommendation: For best results, use Go package versions that follow the Go module versioning (metaversion). However, keep in mind that many Gateway dependencies use basic v1 semantic versioning, which doesn’t always enforce strict versioned import paths.

List plugin symbols

Sometimes it's useful to list symbols from a plugin. For example, we can list the symbols as they are compiled into our testplugin:

text
# nm -gD testplugin.so | grep testplugin
00000000014db4b0 R go:link.pkghashbytes.testplugin
000000000170f7d0 D go:link.pkghash.testplugin
000000000130f5e0 T testplugin.AddFooBarHeader
000000000130f900 T testplugin.AddFooBarHeader.deferwrap1
000000000130f980 T testplugin.AuthCheck
0000000001310100 T testplugin.AuthCheck.deferwrap1
000000000130f540 T testplugin.init
0000000001310ce0 T testplugin.init.0
0000000001ce9580 D testplugin..inittask
0000000001310480 T testplugin.InjectConfigData
0000000001310180 T testplugin.InjectMetadata
0000000001d2a3e0 B testplugin.logger
0000000001310cc0 T testplugin.main
0000000001310820 T testplugin.MakeOutboundCall
0000000001310c40 T testplugin.MakeOutboundCall.deferwrap1

This command prints other symbols that are part of the binary. In the worst case, a build compatibility issue may cause a crash in the Gateway due to an unrecoverable error and this can be used to further debug the binaries produced.

A very basic check to ensure Gateway/plugin compatibility is using the built in go version -m <file>:

text
[output truncated]
	build	-buildmode=exe
	build	-compiler=gc
	build	-race=true
	build	-tags=goplugin
	build	-trimpath=true
	build	CGO_ENABLED=1
	build	GOARCH=amd64
	build	GOOS=linux
	build	GOAMD64=v1
	build	vcs=git
	build	vcs.revision=1db1935d899296c91a55ba528e7b653aec02883b
	build	vcs.time=2024-09-24T12:54:26Z
	build	vcs.modified=false

These options should match between the Gateway binary and the plugin. You can use the command for both binaries and then compare the outputs.


Schemas Swagger (`docs/schemas/swagger.md`)

Tyk API Documentation

The Tyk API Documentation is an OpenAPI specification that outlines the endpoints you can call on the gateway.

Notes

The current OAS version is v3.0.3, generated from gateway v5.6.0.

We are currently using OpenAPI Specification version v3.0.3.

For linting the OAS, we use Redocly. You can install Redocly from here.

How the OAS was generated

We used the Go library openapi-go because:

  1. It supports generating OAS for version v3.0.3 (there are not many Go libraries that produce OAS v3.0.3).
  2. It is highly customizable, allowing you to define how each field is generated.
  3. It lets you write the generation code as functions, making it easier to read and maintain.

How to generate the Swagger.yml file

  1. Ensure that Redocly is installed on your system. You can install Redocly from here.
  2. Clone the gateway repository and check out the branch generate-swagger.
  3. Navigate to the directory swagger/cmd. This directory contains a Makefile with a rule (generate) used to generate the swagger.yml file.
  4. Run the command make generate in this directory.
  5. After running this command, a swagger.yml file containing all the gateway endpoints will be generated in the same directory.
  6. What the make generate command does:
    • The command is defined as: rm -f swagger.yml && go run main.go && redocly lint swagger.yml.
    • It first removes the existing swagger.yml file (if any), generates a new swagger.yml file, and finally lints the file using Redocly.

File Structure

  1. In the Swagger folder, there is a file for each OAS tag (e.g., cache tag, key tag, etc.). If you want to add a new endpoint, add it to its specific tag.
  2. The cmd directory contains the main.go file (used to call all the generation functions), a Makefile, and the generated swagger.yml file.

Plugins Python Taskfile.Yml (`docs/plugins/python/Taskfile.yml`)

yamllint disable rule:line-length


version: "3"

Taskfile tests the provided Dockerfile, see `test` for more details.

This confirms a python environment, not end to end as it doesn't confirm

gateway python plugin loading works. We're missing acceptance tests.

vars:
base: '{{.BASE_IMAGE | default "tykio/tyk-gateway:v5.3.6-rc4"}}'
image: internal/tyk-gateway
platform: '{{.BUILD_PLATFORM | default "linux/amd64"}}'

tasks:
build:
desc: "Build docker images"
vars:
tags: latest
args: --rm --build-arg BASE_IMAGE={{.base}} --platform {{.platform}} -q --no-cache --pull
cmds:
- for:
var: tags
as: tag
cmd: docker build {{.args}} -t {{.image}}:{{.tag}} -f Dockerfile .

test:
desc: "Print python version"
cmds:
- BASE_IMAGE=tykio/tyk-gateway:v5.3.6-rc4 task build
- docker run --rm --entrypoint=/bin/bash {{.image}} -c "python -V"
- docker run --rm --entrypoint=/bin/bash {{.image}} -c "/opt/tyk-gateway/tyk version"
- BASE_IMAGE=tykio/tyk-gateway:v5.3.0 task build
- docker run --rm --entrypoint=/bin/bash {{.image}} -c "python -V"
- docker run --rm --entrypoint=/bin/bash {{.image}} -c "/opt/tyk-gateway/tyk version"


Plugins Tests Taskfile.Yml (`docs/plugins/tests/Taskfile.yml`)


version: "3"

tasks:
all:
desc: "Do everything"
cmds:
- task: checkout
vars:
version: release-5.3.6
- task: clean
- task: workspace
- task: test

checkout:
desc: "Checking out tyk"
cmds:
- git clone --branch {{.version}} https://github.com/TykTechnologies/tyk.git tyk-{{.version}} || true

workspace:
desc: "Create workspace"
vars:
release: tyk-release-5.3.6
commit:
sh: git rev-parse HEAD
go:
sh: go mod edit -json ./{{.release}}/go.mod | jq .Go -r
cmds:
- mkdir -p plugins
- task: workspace:plugins
vars:
go: '{{.go}}'
- go work init ./{{.release}}
- go work use ./plugins
- cd plugins && go get github.com/TykTechnologies/tyk@{{.commit}}

workspace:plugins:
internal: true
desc: "Setup plugins"
dir: plugins
requires:
vars: [go]
cmds:
- rm -f go.mod go.sum
- go mod init testplugin
- go mod edit -go "{{.go}}"
- wget -q https://raw.githubusercontent.com/TykTechnologies/custom-go-plugin/refs/heads/master/go/src/CustomGoPlugin.go

test:
desc: "Test plugin build"
vars:
args: -trimpath -race
cmds:
- cd tyk-release-5.3.6 && go build -tags=goplugin {{.args}} .
- cd plugins && go build {{.args}} -buildmode=plugin .
- ./tyk-release-5.3.6/tyk plugin load -f plugins/testplugin.so -s AuthCheck

clean:
desc: "Clean workspace"
cmds:
- rm -rf plugins go.work*


--- METRICS ---