Rich Plugins
Last updated:
Introduction
Rich plugins make it possible to write powerful middleware for Tyk. Tyk supports:
gRPC provides the ability to write plugins using many languages including C++, Java, Ruby and C#.
The dynamically built Tyk binaries can expose and call Foreign Function Interfaces in guest languages that extend the functionality of a gateway process.
The plugins are able to directly call some Tyk API functions from within their guest language. They can also be configured so that they hook into various points along the standard middleware chain.
Note
When using Python plugins, the middleware function names are set globally. So, if you include two or more plugins that implement the same function, the last declared plugin implementation of the function will be returned. We plan to add namespaces in the future.
How do rich plugins work ?
ID Extractor & Auth Plugins
The ID Extractor is a caching mechanism that’s used in combination with Tyk Plugins. It can be used specifically with plugins that implement custom authentication mechanisms. The ID Extractor works for all rich plugins: gRPC-based plugins, Python and Lua.
See ID Extractor for more details.
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 <code>api.h</code>, where p_data
is a pointer to the serialised data and length
indicates the length of it.
struct CoProcessMessage {
void* p_data;
int length;
};
The unpacked data will hold the actual CoProcessObject
data structure.
HookType
- the hook type (see below)Request
- the HTTP requestSession
- the Tyk session object.Metadata
- the metadata from the session data above (key/value string map).Spec
- the API specification data. Currently organization ID, API ID and config_data.
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. The list, from top to bottom, shows the order of execution. 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 the request is sent to your upstream target and before any authentication information is extracted from the header or parameter list of the request. When enabled, this applies to both keyless and protected APIs.
- AuthCheck: gets executed as a custom authentication middleware, instead of the standard ones provided by Tyk. Use this to provide your own authentication mechanism.
- PostKeyAuth: gets executed right after the authentication process.
- 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. This is only called when using protected APIs. If you want to call a hook after the authentication but before the validation, throttling and other middleware, see PostKeyAuth.
- Response: gets executed after the upstream API replies. The arguments passed to this hook include both the request and response data. Use this to modify the HTTP response before it’s sent to the client. This hook also receives the request object, the session object, the metadata and API definition associated with the request.
Note
Response hooks are not available for native Go plugins. Python and gRPC plugins are supported.
Coprocess Gateway API
<code>coprocess_api.go</code> provides a bridge between the Gateway API and C. Any function that needs to be exported should have the export
keyword:
//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 <code>api.h</code>, like this:
#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 an FFI mechanism could do). As a reference, this is how this could be achieved if you’re building a Cython module:
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:
"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"
}
Note
All hook types support chaining except the custom auth check (auth_check
).
Rich Plugins Data Structures
This page describes the data structures used by Tyk rich plugins, for the following plugin drivers:
- Python (built-in)
- Lua (built-in)
- gRPC (external, compatible with any supported gRPC language)
The Tyk Protocol Buffer definitions are intended for users to generate their own bindings using the appropriate gRPC tools for the required target language. The remainder of this document illustrates a class diagram and explins the attributes of the protobuf messages.
Class Diagram
The class diagram below illustrates the structure of the Object message, dispatched by Tyk to a gRPC server that handles custom plugins.
Object
The Coprocess.Object
data structure wraps a Coprocess.MiniRequestObject
and Coprocess.ResponseObject
It contains additional fields that are useful for users that implement their own request dispatchers, like the middleware hook type and name.
It also includes the session state object (SessionState
), which holds information about the current key/user that’s used for authentication.
message Object {
HookType hook_type = 1;
string hook_name = 2;
MiniRequestObject request = 3;
SessionState session = 4;
map<string, string> metadata = 5;
map<string, string> spec = 6;
ResponseObject response = 7;
}
Field Descriptions
hook_type
Contains the middleware hook type: pre, post, custom auth.
hook_name
Contains the hook name.
request
Contains the request object, see MiniRequestObject
for more details.
session
Contains the session object, see SessionState
for more details.
metadata
Contains the metadata. This is a dynamic field.
spec
Contains information about API definition, including APIID
, OrgID
and config_data
.
response
Contains information populated from the upstream HTTP response data, for response hooks. See ResponseObject for more details. All the field contents can be modified.
MiniRequestObject
The Coprocess.MiniRequestObject
is the main request data structure used by rich plugins. It’s used for middleware calls and contains important fields like headers, parameters, body and URL. A MiniRequestObject
is part of a Coprocess.Object
.
message MiniRequestObject {
map<string, string> headers = 1;
map<string, string> set_headers = 2;
repeated string delete_headers = 3;
string body = 4;
string url = 5;
map<string, string> params = 6;
map<string, string> add_params = 7;
map<string, string> extended_params = 8;
repeated string delete_params = 9;
ReturnOverrides return_overrides = 10;
string method = 11;
string request_uri = 12;
string scheme = 13;
bytes raw_body = 14;
}
Field Descriptions
headers
A read-only field for reading headers injected by previous middleware. Modifying this field won’t alter the request headers See set_headers
and delete_headers
for this.
set_headers
This field appends the given headers (keys and values) to the request.
delete_headers
This field contains an array of header names to be removed from the request.
body
Contains the request body. See ReturnOverrides
for response body modifications.
raw_body
Contains the raw request body (bytes).
url
The request URL.
params
A read-only field that contains the request params. Modifying this value won’t affect the request params.
add_params
Add paramaters to the request.
delete_params
This field contains an array of parameter keys to be removed from the request.
return_overrides
See ReturnOverrides
for more information.
method
The request method, e.g. GET, POST, etc.
request_uri
Raw unprocessed URL which includes query string and fragments.
scheme
Contains the URL scheme, e.g. http
, https
.
ResponseObject
The ResponseObject
exists within an object for response hooks. The fields are populated with the upstream HTTP response data. All the field contents can be modified.
syntax = "proto3";
package coprocess;
message ResponseObject {
int32 status_code = 1;
bytes raw_body = 2;
string body = 3;
map<string, string> headers = 4;
repeated Header multivalue_headers = 5;
}
message Header {
string key = 1;
repeated string values = 2;
}
Field Descriptions
status_code
This field indicates the HTTP status code that was sent by the upstream.
raw_body
This field contains the HTTP response body (bytes). It’s always populated.
body
This field contains the HTTP response body in string format. It’s not populated if the raw_body
contains invalid UTF-8 characters.
headers
A map that contains the headers sent by the upstream.
multivalue_headers
A list of headers, each header in this list is a structure that consists of two parts: a key and its corresponding values.
The key is a string that denotes the name of the header, the values are a list of strings that hold the content of the header, this is useful when the header has multiple associated values.
This field is available for Go, Python and Ruby since tyk v5.0.4 and 5.1.1+.
ReturnOverrides
The ReturnOverrides
object, when returned as part of a Coprocess.Object
, overrides the response of a given HTTP request. It also stops the request flow and the HTTP request isn’t passed upstream. The fields specified in the ReturnOverrides
object are used as the HTTP response.
A sample usage for ReturnOverrides
is when a rich plugin needs to return a custom error to the user.
syntax = "proto3";
package coprocess;
message ReturnOverrides {
int32 response_code = 1;
string response_error = 2;
map<string, string> headers = 3;
bool override_error = 4;
string response_body = 5;
}
Field Descriptions
response_code
This field overrides the HTTP response code and can be used for error codes (403, 500, etc.) or for overriding the response.
response_error
This field overrides the HTTP response body.
headers
This field overrides response HTTP headers.
override_error
This setting provides enhanced customization for returning custom errors. It should be utilized alongside response_body
for optimal effect.
response_body
This field serves as an alias for response_erro
r and holds the HTTP response body.
SessionState
A SessionState
data structure is created for every authenticated request and stored in Redis. It’s used to track the activity of a given key in different ways, mainly by the built-in Tyk middleware like the quota middleware or the rate limiter.
A rich plugin can create a SessionState
object and store it in the same way built-in authentication mechanisms do. This is what a custom authentication middleware does. This is also part of a Coprocess.Object
.
Returning a null session object from a custom authentication middleware is considered a failed authentication and the appropriate HTTP 403 error is returned by the gateway (this is the default behavior) and can be overridden by using ReturnOverrides
.
Field Descriptions
last_check
No longer used.
allowance
No longer in use, should be the same as rate
.
rate
The number of requests that are allowed in the specified rate limiting window.
per
The number of seconds that the rate window should encompass.
expires
An epoch that defines when the key should expire.
quota_max
The maximum number of requests allowed during the quota period.
quota_renews
An epoch that defines when the quota renews.
quota_remaining
Indicates the remaining number of requests within the user’s quota, which is independent of the rate limit.
quota_renewal_rate
The time in seconds during which the quota is valid. So for 1000 requests per hour, this value would be 3600 while quota_max
and quota_remaining
would be 1000.
access_rights
Defined as a map<string, APIDefinition>
instance, that maps the session’s API ID to an AccessDefinition. The AccessDefinition defines the access rights for the API in terms of allowed: versions and URLs(endpoints). Each URL (endpoint) has a list of allowed methods. For further details consult the tutorials for how to create a security policy for Tyk Cloud, Tyk Self Managed and Tyk OSS platforms.
org_id
The organization this user belongs to. This can be used in conjunction with the org_id setting in the API Definition object to have tokens “owned” by organizations.
oauth_client_id
This is set by Tyk if the token is generated by an OAuth client during an OAuth authorization flow.
basic_auth_data
This section contains a hashed representation of the basic auth password and the hashing method used.
For further details see BasicAuthData.
jwt_data
Added to sessions where a Tyk key (embedding a shared secret) is used as the public key for signing the JWT. The JWT token’s KID header value references the ID of a Tyk key. See JWTData for an example.
hmac_enabled
When set to true
this indicates generation of a HMAC signature using the secret provided in hmac_secret
. If the generated signature matches the signature provided in the Authorization header then authentication of the request has passed.
hmac_secret
The value of the HMAC shared secret.
is_inactive
Set this value to true to deny access.
apply_policy_id
The policy ID that is bound to this token.
Note
Although apply_policy_id
is still supported, it is now deprecated. apply_policies
is now used to list your policy IDs as an array. This supports the Multiple Policy feature introduced in the v2.4 - 1.4 release.
data_expires
A value, in seconds, that defines when data generated by this token expires in the analytics DB (must be using Pro edition and MongoDB).
monitor
Defines a quota monitor containing a list of percentage threshold limits in descending order. These limits determine when webhook notifications are triggered for API users or an organization. Each threshold represents a percentage of the quota that, when reached, triggers a notification. See Monitor for further details and an example.
enable_detailed_recording
Set this value to true to have Tyk store the inbound request and outbound response data in HTTP Wire format as part of the analytics data.
metadata
Metadata to be included as part of the session. This is a key/value string map that can be used in other middleware such as transforms and header injection to embed user-specific data into a request, or alternatively to query the providence of a key.
tags
Tags are embedded into analytics data when the request completes. If a policy has tags, those tags will supersede the ones carried by the token (they will be overwritten).
alias
As of v2.1, an Alias offers a way to identify a token in a more human-readable manner, add an Alias to a token in order to have the data transferred into Analytics later on so you can track both hashed and un-hashed tokens to a meaningful identifier that doesn’t expose the security of the underlying token.
last_updated
A UNIX timestamp that represents the time the session was last updated. Applicable to Post, PostAuth and Response plugins. When developing CustomAuth plugins developers should add this to the SessionState instance.
id_extractor_deadline
This is a UNIX timestamp that signifies when a cached key or ID will expire. This relates to custom authentication, where authenticated keys can be cached to save repeated requests to the gRPC server. See id_extractor and Auth Plugins for additional information.
session_lifetime
UNIX timestamp that denotes when the key will automatically expire. Any·subsequent API request made using the key will be rejected. Overrides the global session lifetime. See Key Expiry and Deletion for more information.
AccessDefinition
message AccessDefinition {
string api_name = 1;
string api_id = 2;
repeated string versions = 3;
repeated AccessSpec allowed_urls = 4;
}
Defined as an attribute within a SessionState instance. Contains the allowed versions and URLs (endpoints) for the API that the session request relates to. Each URL (endpoint) specifies an associated list of allowed methods. See also AccessSpec.
Field Descriptions
api_name
The name of the API that the session request relates to.
api_id
The ID of the API that the session request relates to.
versions
List of allowed API versions, e.g. "versions": [ "Default" ]
.
allowed_urls
List of AccessSpec instances. Each instance defines a URL (endpoint) with an associated allowed list of methods. If all URLs (endpoints) are allowed then the attribute is not set.
AccessSpec
Defines an API’s URL (endpoint) and associated list of allowed methods
message AccessSpec {
string url = 1;
repeated string methods = 2;
}
Field Descriptions
url
A URL (endpoint) belonging to the API associated with the request session.
methods
List of allowed methods for the URL (endpoint), e.g. "methods": [ "GET". "POST", "PUT", "PATCH" ]
.
BasicAuthData
The BasicAuthData
contains a hashed password and the name of the hashing algorithm used. This is represented by the basic_auth_data
attribute in SessionState message.
"basicAuthData": {
"password": <a_hashed_password_presentation>,
"hash": <the_hashing_algorithm_used_to_hash_the_password>
}
Field Descriptions
password
A hashed password.
hash
Name of the hashing algorithm used to hash the password.
JWTData
Added to sessions where a Tyk key (embedding a shared secret) is used as the public key for signing the JWT. This message contains the shared secret.
"jwtData": {
"secret": "the_secret"
}
Field Descriptions
secret
The shared secret.
Monitor
Added to a session when monitor quota thresholds are defined within the Tyk key. This message contains the quota percentage threshold limits, defined in descending order, that trigger webhook notification.
message Monitor {
repeated double trigger_limits = 1;
}
Field Descriptions
trigger_limits
List of trigger limits defined in descending order. Each limit represents the percentage of the quota that must be reached in order for the webhook notification to be triggered.
"monitor": {
"trigger_limits": [80.0, 60.0, 50.0]
}
Using Python
Overview
Requirements
Since v2.9, Tyk supports any currently stable Python 3.x version. The main requirement is to have the Python shared libraries installed. These are available as libpython3.x
in most Linux distributions.
- Python3-dev
- Protobuf: provides Protocol Buffers support
- gRPC: provides gRPC support
Important Note Regarding Performance
Python plugins are embedded within the Tyk Gateway process. Tyk Gateway integrates with Python custom plugins via a cgo bridge.
Tyk Gateway
<-> CGO <-> Python Custom Plugin
In order to integrate with Python custom plugins, the libpython3.x.so shared object library is used to embed a Python interpreter directly in the Tyk Gateway. Further details can be found here
This allows combining the strengths of both Python and Go in a single application. However, it’s essential to be aware of the potential complexities and performance implications of mixing languages, as well as the need for careful memory management when working with Python objects from Go.
The Tyk Gateway process initialises the Python interpreter using Py_initialize. The Python Global Interpreter Lock (GIL) allows only one thread to execute Python bytecode at a time, ensuring thread safety and simplifying memory management. While the GIL simplifies these aspects, it can limit the scalability of multi-threaded applications, particularly those with CPU-bound tasks, as it restricts parallel execution of Python code.
In the context of custom Python plugins, API calls are queued and the Python interpreter handles requests sequentially, processing them one at a time. Subsequently, this would consume large amounts of memory, and network sockets would remain open and blocked until the API request is processed.
Install the Python development packages
Note
Starting from Tyk Gateway version v5.3.0
, Python is no longer bundled with the official Tyk Gateway Docker image by default, to address security vulnerabilities in the Python libraries highlighted by Docker Scout.
Whilst Python plugins are still supported by Tyk Gateway, if you want to use them you must extend the image to add support for Python. For further details, please refer to the release notes for Tyk Gateway v5.3.0
.
If you wish to use Python plugins using Docker, you can extend the official Tyk Gateway Docker image by adding Python to it.
This example Dockerfile extends the official Tyk Gateway image to support Python plugins by installing python and the required modules:
ARG BASE_IMAGE
FROM ${BASE_IMAGE} AS base
FROM python:3.11-bookworm
COPY --from=base /opt/tyk-gateway/ /opt/tyk-gateway/
RUN pip install setuptools && pip install google && pip install 'protobuf==4.24.4'
EXPOSE 8080 80 443
ENV PYTHON_VERSION=3.11
ENV PORT=8080
WORKDIR /opt/tyk-gateway/
ENTRYPOINT ["/opt/tyk-gateway/tyk" ]
CMD [ "--conf=/opt/tyk-gateway/tyk.conf" ]
To use this, you simply run docker build
with this Dockerfile, providing the Tyk Gateway image that you would like to extend as build argument BASE_IMAGE
.
As an example, this command will extend Tyk Gateway v5.3.0
to support Python plugins, generating the image tyk-gateway-python:v5.3.0
:
docker build --build-arg BASE_IMAGE=tykio/tyk-gateway:v5.3.0 -t tyk-gateway-python:v5.3.0 .
apt install python3 python3-dev python3-pip build-essential
Install the Required Python Modules
Make sure that “pip” is available in your system, it should be typically available as “pip”, “pip3” or “pipX.X” (where X.X represents the Python version):
pip3 install protobuf grpcio
yum install python3-devel python3-setuptools
python3 -m ensurepip
Install the Required Python Modules
Make sure that “pip” is now available in your system, it should be typically available as “pip”, “pip3” or “pipX.X” (where X.X represents the Python version):
pip3 install protobuf grpcio
Python versions
Newer Tyk versions provide more flexibility when using Python plugins, allowing the users to set which Python version to use. By default, Tyk will try to use the latest version available.
To see the Python initialisation log, run the Tyk gateway in debug mode.
To use a specific Python version, set the python_version
flag under coprocess_options
in the Tyk Gateway configuration file (tyk.conf).
Note
Tyk doesn’t support Python 2.x.
Troubleshooting
To verify that the required Python Protocol Buffers module is available:
python3 -c 'from google import protobuf'
No output is expected from this command on successful setups.
How do I write Python Plugins?
We have created a demo Python plugin repository.
The project implements a simple middleware for header injection, using a Pre hook (see Tyk custom middleware hooks. A single Python script contains the code for it, see middleware.py.
Custom Authentication Plugin Tutorial
Introduction
This tutorial will guide you through the creation of a custom authentication plugin, written in Python. A custom authentication plugin allows you to implement your own authentication logic and override the default Tyk authentication mechanism. The sample code implements a very simple key check; currently it supports a single, hard-coded key. It could serve as a starting point for your own authentication logic. We have tested this plugin with Ubuntu 14.
The code used in this tutorial is also available in this GitHub repository.
Requirements
- Tyk API Gateway: This can be installed using standard package management tools like Yum or APT, or from source code. See here for more installation options.
Dependencies
- The Tyk CLI utility, which is bundled with our RPM and DEB packages, and can be installed separately from https://github.com/TykTechnologies/tyk-cli
- In Tyk 2.8 the Tyk CLI is part of the gateway binary, you can find more information by running “tyk help bundle”.
- Python 3.4
Create the Plugin
The first step is to create a new directory for your plugin file:
mkdir ~/my-tyk-plugin
cd ~/my-tyk-plugin
Next you need to create a manifest file. This file contains information about our plugin file structure and how you expect it to interact with the API that will load it.
This file should be named manifest.json
and needs to contain the following content:
{
"file_list": [
"middleware.py"
],
"custom_middleware": {
"driver": "python",
"auth_check": {
"name": "MyAuthMiddleware"
}
}
}
- The
file_list
block contains the list of files to be included in the bundle, the CLI tool expects to find these files in the current working directory. - The
custom_middleware
block contains the middleware settings like the plugin driver we want to use (driver
) and the hooks that our plugin will expose. You use theauth_check
for this tutorial. For other hooks see here. - The
name
field references the name of the function that you implement in your plugin code:MyAuthMiddleware
. - You add an additional file called
middleware.py
, this will contain the main implementation of our middleware.
Note
Your bundle should always contain a file named middleware.py
as this is the entry point file.
Contents of middleware.py
You import decorators from the Tyk module as this gives you the Hook
decorator, and you import Tyk Python API helpers
You implement a middleware function and register it as a hook, the input includes the request object, the session object, the API meta data and its specification:
from tyk.decorators import *
from gateway import TykGateway as tyk
@Hook
def MyAuthMiddleware(request, session, metadata, spec):
auth_header = request.get_header('Authorization')
if auth_header == '47a0c79c427728b3df4af62b9228c8ae':
tyk.log("I'm logged!", "info")
tyk.log("Request body" + request.object.body, "info")
tyk.log("API config_data" + spec['config_data'], "info")
session.rate = 1000.0
session.per = 1.0
metadata["token"] = "47a0c79c427728b3df4af62b9228c8ae"
return request, session, metadata
You can modify the manifest.json
to add as many files as you want. Files that aren’t listed in the manifest.json
file will be ignored when building the plugin bundle.
Building the Plugin
A plugin bundle is a packaged version of the plugin, it may also contain a cryptographic signature of its contents. The -y
flag tells the Tyk CLI tool to skip the signing process in order to simplify the flow of this tutorial. For more information on the Tyk CLI tool, see here.
You will use the Dockerised version of the Tyk CLI tool to bundle our package.
First, export your Tyk Gateway version to a variable.
##### THIS MUST MATCH YOUR TYK GATEWAY VERSION
$ IMAGETAG=v3.1.2
Then run the following commands to generate a bundle.zip
in your current directory:
$ docker run \
--rm -w "/tmp" -v $(pwd):/tmp \
--entrypoint "/bin/sh" -it \
tykio/tyk-gateway:$IMAGETAG \
-c '/opt/tyk-gateway/tyk bundle build -y'
Success!
You should now have a bundle.zip
file in the plugin directory.
Publishing the Plugin
To allow Tyk access to the plugin bundle, you need to serve this file using a web server. For this tutorial we’ll use the Python built-in HTTP server (check the official docs for additional information). This server listens on port 8000 by default. To start it use:
python3 -m http.server
When the server is started our current working directory is used as the web root path, this means that our bundle.zip
file should be accessible from the following URL:
http://<IP Address>:8000/bundle.zip
The Tyk Gateway fetches and loads a plugin bundle during startup time and subsequent reloads. For updating plugins using the hot reload feature, you should use different plugin bundle names as you expect them to be used for versioning purposes, e.g. bundle-1, bundle-2, etc. If a bundle already exists, Tyk will skip the download process and load the version that’s already present.
Configure Tyk
You will need to modify the Tyk global configuration file (tyk.conf
) to use Python plugins. The following block should be present in this file:
"coprocess_options": {
"enable_coprocess": true,
"python_path_prefix": "/opt/tyk-gateway"
},
"enable_bundle_downloader": true,
"bundle_base_url": "http://dummy-bundle-server.com/bundles/",
"public_key_path": "/path/to/my/pubkey"
Options
enable_coprocess
: This enables the pluginpython_path_prefix
: Sets the path to built-in Tyk modules, this will be part of the Python module lookup path. The value used here is the default one for most installations.enable_bundle_downloader
: This enables the bundle downloaderbundle_base_url
: This is a base URL that will be used to download the bundle. You should replace thebundle_base_url
with the appropriate URL of the web server that’s serving your plugin bundles. For now HTTP and HTTPS are supported but we plan to add more options in the future (like pulling directly from S3 buckets). You use the URL that’s exposed by the test HTTP server in the previous step.public_key_path
: Modifypublic_key_path
in case you want to enforce the cryptographic check of the plugin bundle signatures. If thepublic_key_path
isn’t set, the verification process will be skipped and unsigned plugin bundles will be loaded normally.
Configure an API Definition
There are two important parameters that you need to add or modify in the API definition.
The first one is custom_middleware_bundle
which must match the name of the plugin bundle file. If we keep this with the default name that the Tyk CLI tool uses, it will be bundle.zip
.
"custom_middleware_bundle": "bundle.zip"
The second parameter is specific to this tutorial, and should be used in combination with use_keyless
to allow an API to authenticate against our plugin:
"use_keyless": false
"enable_coprocess_auth": true
"enable_coprocess_auth"
will instruct the Tyk gateway to authenticate this API using the associated custom authentication function that’s implemented by the plugin.
Configuration via the Tyk Dashboard
To attach the plugin to an API, From the Advanced Options tab in the API Designer enter bundle.zip in the Plugin Bundle ID field.
You also need to modify the authentication mechanism that’s used by the API. From the Core Settings tab in the API Designer select Use Custom Authentication (Python, CoProcess, and JSVM plugins) from the Authentication - Authentication Mode drop-down list.
Testing the Plugin
Now you can simply make an API call against the API for which we’ve loaded the Python plugin.
If Running Tyk Gateway from Source
At this point you have your test HTTP server ready to serve the plugin bundle and the configuration with all the required parameters.
The final step is to start or restart the Tyk Gateway (this may vary depending on how you setup Tyk).
A separate service is used to load the Tyk version that supports Python (tyk-gateway-python
), so we need to stop the standard one first (tyk-gateway
):
service tyk-gateway stop
service tyk-gateway-python start
From now on you should use the following command to restart the service:
service tyk-gateway-python restart
A cURL request will be enough for testing our custom authentication middleware.
This request will trigger a bad authentication:
curl http://<IP Address>:8080/my-api/my-path -H 'Authorization: badtoken'
This request will trigger a successful authentication. You are using the token that’s set by your Python plugin:
curl http://<IP Address>:8080/my-api/my-path -H 'Authorization: 47a0c79c427728b3df4af62b9228c8ae'
What’s Next?
In this tutorial you learned how Tyk plugins work. For a production-level setup we suggest the following steps:
- Configure Tyk to use your own key so that you can enforce cryptographic signature checks when loading plugin bundles, and sign your plugin bundles!
- Configure an appropriate web server and path to serve your plugin bundles.
Add Python Plugin To Your Gateway
API settings
To add a Python plugin to your API, you must specify the bundle name using the custom_middleware_bundle
field:
{
"name": "Tyk Test API",
"api_id": "1",
"org_id": "default",
"definition": {
"location": "header",
"key": "version"
},
"auth": {
"auth_header_name": "authorization"
},
"use_keyless": true,
"version_data": {
"not_versioned": true,
"versions": {
"Default": {
"name": "Default",
"expires": "3000-01-02 15:04",
"use_extended_paths": true,
"extended_paths": {
"ignored": [],
"white_list": [],
"black_list": []
}
}
}
},
"proxy": {
"listen_path": "/quickstart/",
"target_url": "http://httpbin.org",
"strip_listen_path": true
},
"custom_middleware_bundle": "test-bundle"
}
Global settings
To enable Python plugins you need to add the following block to tyk.conf
:
"coprocess_options": {
"enable_coprocess": true,
"python_path_prefix": "/opt/tyk-gateway"
},
"enable_bundle_downloader": true,
"bundle_base_url": "http://dummy-bundle-server.com/bundles/",
"public_key_path": "/path/to/my/pubkey",
enable_coprocess
: enables the rich plugins feature.
python_path_prefix
: Sets the path to built-in Tyk modules, this will be part of the Python module lookup path. The value used here is the default one for most installations.
enable_bundle_downloader
: enables the bundle downloader.
bundle_base_url
: is 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 URL for your specified bundle server (in the above example): dummy-bundle-server.com/bundles/test-bundle
. You need to create and then specify your own bundle server URL.
public_key_path
: sets a public key, this is used for verifying signed bundles, you may omit this if unsigned bundles are used.
Tyk Python API methods
Python plugins may call these Tyk API methods:
store_data(key, value, ttl)
store_data
sets a Redis key
with the specified value
and ttl
.
get_data(key)
get_data
retrieves a Redis key
.
trigger_event(event_name, payload)
trigger_event
triggers an internal Tyk event, the payload
must be a JSON object.
log(msg, level)
log
will log a message (msg
) using the specified level
.
log_error(*args)
log_error
is a shortcut for log
, it uses the error log level.
Python Performance
These are some benchmarks performed on Python plugins. Python plugins run in a standard Python interpreter, embedded inside Tyk.
Using gRPC
Overview
gRPC is a very powerful framework for RPC communication across different languages. It was created by Google and makes heavy use of HTTP2 capabilities and the Protocol Buffers serialisation mechanism to dispatch and exchange requests between Tyk and your gRPC plugins.
When it comes to built-in plugins, we have been able to integrate several languages like Python, Javascript & Lua in a native way: this means the middleware you write using any of these languages runs in the same process. At the time of writing, the following languages are supported: C++, Java, Objective-C, Python, Ruby, Go, C# and Node.JS.
For supporting additional languages we have decided to integrate gRPC connections and perform the middleware operations within a gRPC server that is external to the Tyk process. Please contact us to learn more:
Tyk has built-in support for gRPC backends, enabling you to build rich plugins using any of the gRPC supported languages. See gRPC by language for further details.
Architectural overview
An example architecture is illustrated below.
Here we can see that Tyk Gateway sends requests to an external Java gRPC server to handle authentication, via a CustomAuth plugin. The flow is as follows:
- Tyk receives a HTTP request.
- Tyk serialises the request and session into a protobuf message that is dispatched to your gRPC server.
- The gRPC server performs custom middleware operations (for example, any modification of the request object). Each plugin (Pre, PostAuthKey, Post, Response etc.) is handled as separate gRPC request.
- The gRPC server sends the request back to Tyk.
- Tyk proxies the request to your upstream API.
Use cases
Deploying an external gRPC server to handle plugins provides numerous technical advantages:
- Allows for independent scalability of the service from the Tyk Gateway.
- Utilizes a custom-designed server tailored to address specific security concerns, effectively mitigating various security risks associated with native plugins.
Limitations
At the time of writing the following features are currently unsupported and unavailable in the serialised request:
- Client certificiates
- OAuth keys
- For graphQL APIs details concerning the max_query_depth is unavailable
- A request query parameter cannot be associated with multiple values
Developer Resources
The Protocol Buffers and bindings provided by Tyk should be used in order for successful ommunication between Tyk Gateway and your gRPC plugin server. Documentation for the protobuf messages is available in the Rich Plugins Data Structures page.
You can generate supporting HTML documentation using the docs task in the Taskfile file of the Tyk repository. This documentation explains the protobuf messages and services that allow gRPC plugins to handle a request made to the Gateway. Please refer to the README file within the proto folder of the tyk repository for further details.
You may re-use the bindings that were generated for our samples or generate the bindings youself for Go, Python and Ruby, as implemented by the generate task in the Taskfile file of the Tyk repository.
If you wish to generate bindings for another target language you may generate the bindings yourself. The Protocol Buffers and gRPC documentation provide specific requirements and instructions for each language.
What’s next?
See our getting started guide for an explanation of how to write and configure gRPC plugins.
Key Concepts
This document serves as a developer’s guide for understanding the key concepts and practical steps for writing and configuring gRPC plugins for Tyk Gateway. It provides technical insights and practical guidance to seamlessly integrate Tyk plugins into your infrastructure through gRPC. The goal is to equip developers with the knowledge and tools needed to effectively utilize gRPC for enhancing Tyk Gateway functionalities.
This comprehensive guide covers essential tasks, including:
-
Developing a gRPC Server: Learn how to develop a gRPC server using Tyk protocol buffers. The gRPC server facilitates the execution of Tyk plugins, which offer custom middleware for various phases of the API request lifecycle. By integrating these plugins, developers can enable Tyk Gateway with enhanced control and flexibility in managing API requests, allowing for fine-grained customization and tailored processing at each stage of the request lifecycle.
-
Configuring Tyk Gateway: Set up Tyk Gateway to communicate with your gRPC Server and, optionally, an external secured web server hosting the gRPC plugin bundle for API configurations. Configure Tyk Gateway to fetch the bundle configured for an API from the web server, enabling seamless integration with gRPC plugins. Specify connection settings for streamlined integration.
-
API Configuration: Customize API settings within Tyk Gateway to configure gRPC plugin utilization. Define plugin hooks directly within the API Definition or remotely via an external web server for seamless request orchestration. Tyk plugins provide custom middleware for different phases of the API request lifecycle, enhancing control and flexibility.
-
API Testing: Test that Tyk Gateway integrates with your gRPC server for the plugins configured for your API.
Develop gRPC server
Develop your gRPC server, using your preferred language, to handle requests from Tyk Gateway for each of the required plugin hooks. These hooks allow Tyk Gateway to communicate with your gRPC server to execute custom middleware at various stages of the API request lifecycle.
Prerequisites
The following prerequisites are necessary for developing a gRPC server that integrates with Tyk Gateway.
####### Tyk gRPC Protocol Buffers
A collection of Protocol Buffer messages are available in the Tyk Gateway repository to allow Tyk Gateway to integrate with your gRPC server, requesting execution of plugin code. These messages establish a standard set of data structures that are serialised between Tyk Gateway and your gRPC Server. Developers should consult the Rich Plugins Data Structures page for further details.
####### Protocol Buffer Compiler
The protocol buffer compiler, protoc
, should be installed to generate the service and data structures in your preferred language(s) from the Tyk gRPC Protocol Buffer files. Developers should consult the installation documentation at grpc.io for an explanation of how to install protoc
.
Generate Bindings
Generate the bindings (service and data structures) for your target language using the protoc
compiler. Tutorials are available at protobuf.dev for your target language.
Implement service
Your gRPC server should implement the Dispatcher service to enable Tyk Gateway to integrate with your gRPC server. The Protocol Buffer definition for the Dispatcher service is listed below:
service Dispatcher {
rpc Dispatch (Object) returns (Object) {}
rpc DispatchEvent (Event) returns (EventReply) {}
}
The Dispatcher service contains two RPC methods, Dispatch and DispatchEvent. Dispatch handles a requests made by Tyk Gateway for each plugin configured in your API. DispatchEvent receives notification of an event.
Your Dispatch RPC should handle the request made by Tyk Gateway, implementing custom middleware for the intended plugin hooks. Each plugin hook allows Tyk Gateway to communicate with your gRPC server to execute custom middleware at various stages of the API request lifecycle, such as Pre, PostAuth, Post, Response etc. The Tyk Protocol Buffers define the HookType enumeration to inspect the type of the intended gRPC plugin associated with the request. This is accessible as an attribute on the Object message, e.g. object_message_instance.hook_type.
Developer resources
Consult the Tyk protocol buffers for the definition of the service and data structures that enable integration of Tyk gateway with your gRPC server. Tyk provides pre-generated bindings for C++, Java, Python and Ruby.
Example tutorials are available that explain how to generate the protobuf bindings and implement a server for Java, .NET and NodeJS.
Tyk Github repositories are also available with examples for Ruby and C#/.NET
Configure Tyk Gateway
Configure Tyk Gateway to issue requests to your gRPC server and optionally, specify the URL of the web server that will serve plugin bundles.
Configure gRPC server
Modify the root of your tyk.conf
file to include the coprocess_options section, similar to that listed below:
"coprocess_options": {
"enable_coprocess": true,
"coprocess_grpc_server": "tcp://127.0.0.1:5555",
"grpc_authority": "localhost",
"grpc_recv_max_size": 100000000,
"grpc_send_max_size": 100000000
},
A gRPC server can configured under the coprocess_options
section as follows:
enable_coprocess
: Enables the rich plugins feature.coprocess_grpc_server
: Specifies the gRPC server URL, in this example we’re using TCP. Tyk will attempt a connection on startup and keep reconnecting in case of failure.grpc_recv_max_size
: Specifies the message size supported by the gateway gRPC client, for receiving gRPC responses.grpc_send_max_size
: Specifies the message size supported by the gateway gRPC client for sending gRPC requests.grpc_authority
: Theauthority
header value, defaults tolocalhost
if omitted. Allows configuration according to RFC 7540.
When using gRPC plugins, Tyk acts as a gRPC client and dispatches requests to your gRPC server. gRPC libraries usually set a default maximum size, for example, the official gRPC Java library establishes a 4 MB message size https://jbrandhorst.com/post/grpc-binary-blob-stream/.
Configuration parameters are available for establishing a message size in both directions (send and receive). For most use cases and especially if you’re dealing with multiple hooks, where the same request object is dispatched, it is recommended to set both values to the same size.
Configure Web server (optional)
Tyk Gateway can be configured to download the gRPC plugin configuration for an API from a web server. For further details related to the concept of bundling plugins please refer to plugin bundles.
"enable_bundle_downloader": true,
"bundle_base_url": "https://my-bundle-server.com/bundles/",
"public_key_path": "/path/to/my/pubkey",
The following parameters can be configured:
enable_bundle_downloader
: Enables the bundle downloader to download bundles from a webserver.bundle_base_url
: Base URL from which to serve bundled plugins.public_key_path
: Public key for bundle verification (optional)
The public_key_path
value is used for verifying signed bundles, you may omit this if unsigned bundles are used.
Configure API
Plugin hooks for your APIs in Tyk can be configured either by directly specifying them in a configuration file on the Gateway server or by hosting the configuration externally on a web server. This section explains how to configure gRPC plugins for your API endpoints on the local Gateway or remotely from an external secured web server.
Local
This section provides examples for how to configure gRPC plugin hooks, locally within an API Definition. Examples are provided for Tyk Gateway and Tyk Operator.
Tyk Gateway
For configurations directly embedded within the Tyk Gateway, plugin hooks can be defined within your API Definition. An example snippet from a Tyk Classic API Definition is provided below:
"custom_middleware": {
"pre": [
{"name": "MyPreMiddleware"}
],
"post": [
{"name": "MyPostMiddleware"}
],
"auth_check": {
"name": "MyAuthCheck"
},
"driver": "grpc"
}
For example, a Post request plugin hook has been configured with name MyPostMiddleware
. Before the request is sent upstream Tyk Gateway will serialize the request into a Object protobuf message with the hook_name
property set to MyPostMiddleware
and the hook_type
property set to Post
. This message will then then be dispatched to the gRPC server for processing before the request is sent upstream.
Note
Ensure the plugin driver is configured as type grpc. Tyk will issue a request to your gRPC server for each plugin hook that you have configured.
Tyk Operator
The examples below illustrate how to configure plugin hooks for an API Definition within Tyk Operator.
Setting the driver
configuring parameter to gRPC
instructs Tyk Gateway to issue a request to your gRPC server for each plugin hook that you have configured.
Pre plugin hook example
In this example we can see that a custom_middleware
configuration block has been used to configure a gRPC Pre request plugin hook with name HelloFromPre
. Before any middleware is executed Tyk Gateway will serialize the request into a Object protobuf message with the hook_name
property set to HelloFromPre
and the hook_type
property set to Pre
. This message will then then be dispatched to the gRPC server.
|
|
Post plugin hook example
In the example we can see that a custom_middleware
configuration block has been used to configure a gRPC Post plugin with name HelloFromPost
.
Before the request is sent upstream Tyk Gateway will serialize the request and session details into a Object protobuf message with the hook_name
property set to HelloFromPost
and the hook_type
property set to Post
. This message will then then be dispatched to the gRPC server for processing before the request is sent upstream.
|
|
Remote
It is possible to configure your API so that it downloads a bundled configuration of your plugins from an external webserver. The bundled plugin configuration is contained within a zip file.
A gRPC plugin bundle is similar to the standard bundling mechanism. The standard bundling mechanism zips the configuration and plugin source code, which will be executed by Tyk. Conversely, a gRPC plugin bundle contains only the configuration (manifest.json
), with plugin code execution being handled independently by the gRPC server.
Bundling a gRPC plugin requires the following steps:
- Create a
manifest.json
that contains the configuration of your plugins - Build a zip file that bundles your plugin
- Upload the zip file to an external secured webserver
- Configure your API to download your plugin bundle
Create the manifest file
The manifest.json
file specifies the configuration for your gRPC plugins. An example manifest.json
is listed below:
{
"file_list": [],
"custom_middleware": {
"pre": [{"name": "MyPreMiddleware"}],
"post": [{"name": "MyPostMiddleware"}],
"auth_check": {"name": "MyAuthCheck"},
"driver": "grpc"
},
"checksum": "",
"signature": ""
}
Note
The source code files, file_list, are empty for gRPC plugins. Your gRPC server contains the source code for handling plugins.
Build plugin bundle
A plugin bundle can be built using the Tyk Gateway binary and should only contain the manifest.json
file:
tyk bundle build -output mybundle.zip -key mykey.pem
The example above generates a zip file, name mybundle.zip
. The zip file is signed with key mykey.pem
.
The resulting bundle file should then be uploaded to the webserver that hosts your plugin bundles.
Configure API
####### Tyk Gateway
To add a gRPC plugin to your API definition, you must specify the bundle file name within the custom_middleware_bundle
field:
{
"name": "Tyk Test API",
...
+ "custom_middleware_bundle": "mybundle.zip"
}
The value of the custom_middleware_bundle
field will be used in combination with the gateway settings to construct a bundle URL. For example, if Tyk Gateway is configured with a webserver base URL of https://my-bundle-server.com/bundles/ then an attempt would be made to download the bundle from https://my-bundle-server.com/bundles/mybundle.zip.
####### Tyk Operator
Currently this feature is not yet documented with a Tyk Operator example for configuring an API to use plugin bundles. For further details please reach out and contact us on the community support forum.
Test your API Endpoint
It is crucial to ensure the security and reliability of your gRPC server. As the developer, it is your responsibility to verify that your gRPC server is secured and thoroughly tested with appropriate test coverage. Consider implementing unit tests, integration tests and other testing methodologies to ensure the robustness of your server’s functionality and security measures. This step ensures that the Tyk Gateway properly communicates with your gRPC server and executes the custom logic defined by the plugin hooks.
Test the API endpoint using tools like Curl or Postman. Ensure that your gRPC server is running and the gRPC plugin(s) are functioning. An example using Curl is listed below:
curl -X GET https://www.your-gateway-server.com:8080/api/path
Replace https://www.your-gateway-server.com:8080/api/path
with the actual endpoint of your API.
Summary
This guide has explained the key concepts and processes for writing gRPC plugins that integrate with Tyk Gateway. The following explanations have been given:
- Prerequisites for developing a gRPC server for your target language.
- The Dispatcher service interface.
- How to configure Tyk Gateway to integrate with your gRPC server.
- How to configure Tyk Gateway with an optional external web server for fetching plugin configuration.
- How to configure gRPC plugins for your APIs.
- How to test your API integration with your gRPC server using curl.
What’s Next?
- Consult the Protocol Buffer messages that Tyk Gateway uses when making a request to a gRPC server.
- Visit tutorial guides that explain how to implement a Java, .NET and NodeJS gRPC server.
- Visit our plugins hub to explore further gRPC development examples and resources.
Getting Started: Creating A Python gRPC Server
In the realm of API integration, establishing seamless connections between services is paramount.
Understanding the fundamentals of gRPC server implementation is crucial, especially when integrating with a Gateway solution like Tyk. This guide aims to provide practical insights into this process, starting with the basic principles of how to implement a Python gRPC server that integrates with Tyk Gateway.
Objectives
By the end of this guide, you will be able to implement a gRPC server that will integrate with Tyk Gateway, setting the stage for further exploration in subsequent parts:
- Establishing the necessary tools, Python libraries and gRPC service definition for implementing a gRPC server that integrates with Tyk Gateway.
- Developing a basic gRPC server that echoes the request payload to the console, showcasing the core principles of integration.
- Configuring Tyk Gateway to interact with our gRPC server, enabling seamless communication between the two services.
Before implementing our first gRPC server it is first necessary to understand the service interface that defines how Tyk Gateway integrates with a gRPC server.
Tyk Dispatcher Service
The Dispatcher service, defined in the coprocess_object.proto file, contains the Dispatch RPC method, invoked by Tyk Gateway to request remote execution of gRPC plugins. Tyk Gateway dispatches accompanying data relating to the original client request and session. The service definition is listed below:
service Dispatcher {
rpc Dispatch (Object) returns (Object) {}
rpc DispatchEvent (Event) returns (EventReply) {}
}
On the server side, we will implement the Dispatcher service methods and a gRPC server to handle requests from Tyk Gateway. The gRPC infrastructure decodes incoming requests, executes service methods and encodes service responses.
Before we start developing our gRPC server we need to setup our development environment with the supporting libraries and tools.
Prerequisites
Firstly, we need to download the Tyk Protocol Buffers and install the Python protoc compiler.
We are going to use the protoc compiler to generate the supporting classes and data structures to implement the Dispatcher service.
Tyk Protocol Buffers
Issue the following command to download and extract the Tyk Protocol Buffers from the Tyk GitHub repository:
curl -sL "https://github.com/TykTechnologies/tyk/archive/master.tar.gz " -o tyk.tar.gz && \
mkdir tyk && \
tar -xzvf tyk.tar.gz --strip-components=1 -C tyk && \
mv tyk/coprocess/proto/* . && \
rm -r tyk tyk.tar.gz
Install Dependencies
We are going to setup a Python virtual environment and install some supporting dependencies. Assuming that you have Python virtualenv already installed, then issue the following commands to setup a Python virtual environment containing the grpcio and grpcio-tools libraries:
python3 -m venv .venv
source .venv/bin/activate
pip install –upgrade pip
pip install grpcio grpcio-tools grpcio-reflection
The grpcio library offers essential functionality to support core gRPC features such as message serialisation and deserialisation. The grpcio-tools library provides the Python protoc compiler that we will use to generate the supporting classes and data structures to implement our gRPC server. The grpcio-reflection library allows clients to query information about the services and methods provided by a gRPC server at runtime. It enables clients to dynamically discover available services, their RPC methods, in addition to the message types and field names associated with those methods.
Install grpcurl
Follow the installation instructions to install grpcurl. We will use grpcurl to send test requests to our gRPC server.
Generate Python Bindings
We are now able to generate the Python classes and data structures to allow us to implement our gRPC server. To accomplish this we will use the Python protoc command as listed below:
python -m grpc_tools.protoc --proto_path=. --python_out=. --grpc_python_out=. *.proto
This compiles the Protocol Buffer files (*.proto) from the current working directory and generates the Python classes representing the Protocol Buffer messages and services. A series of .py files should now exist in the current working directory. We are interested in the coprocess_object_pb2_grpc.py file, containing a default implementation of Tyk’s Dispatcher service.
Inspect the generated Python file, coprocess_object_pb2_grpc.py, containing the DispatcherServicer class:
class DispatcherServicer(object):
""" GRPC server interface, that must be implemented by the target language """
def Dispatch(self, request, context):
""" Accepts and returns an Object message """
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def DispatchEvent(self, request, context):
""" Dispatches an event to the target language """
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
This superclass contains a default stub implementation for the Dispatch and DispatchEvent RPC methods, each defining request and context parameters:
The request parameter allows our server to access the message payload sent by Tyk Gateway. We can use this data, pertaining to the request and session, to process and generate a response.
The context parameter provides additional information and functionalities related to the RPC call, such as timeout limits, cancelation signals etc. This is a grpc.ServicerContext or a grpc.aio.ServicerContext, object depending upon whether a synchronous or AsyncIO gRPC server is implemented.
In the next step we will implement a subclass that will handle requests made by Tyk Gateway for remote execution of custom plugins.
Implement Dispatcher Service
We will now develop the Dispatcher service, adding implementations of the Dispatch and DispatchEvent methods, to allow our gRPC server to integrate with Tyk Gateway. Before we continue, create a file, async_server.py, within the same folder as the generated Protocol Buffer (.proto) files.
Dispatch
Our implementation of the Dispatch RPC method will deserialize the request payload and output to the console as JSON format. This serves as a useful development and debugging aid, allowing inspection of the request and session state dispatched by Tyk Gateway to our gRPC server.
Copy and paste the following source code into the async_server.py file. Notice that we have used type hinting to aid readability. The type hints are located within the type hint files (.pyi) we generated with the protoc compiler.
import asyncio
import grpc
import json
import signal
import logging
from google.protobuf.json_format import MessageToJson
from grpc_reflection.v1alpha import reflection
import coprocess_object_pb2_grpc
import coprocess_object_pb2
from coprocess_common_pb2 import HookType
from coprocess_session_state_pb2 import SessionState
class PythonDispatcher(coprocess_object_pb2_grpc.DispatcherServicer):
async def Dispatch(
self, object: coprocess_object_pb2.Object, context: grpc.aio.ServicerContext
) -> coprocess_object_pb2.Object:
logging.info(f"STATE for {object.hook_name}\n{MessageToJson(object)}\n")
if object.hook_type == HookType.Pre:
logging.info(f"Pre plugin name: {object.hook_name}")
logging.info(f"Activated Pre Request plugin from API: {object.spec.get('APIID')}")
elif object.hook_type == HookType.CustomKeyCheck:
logging.info(f"CustomAuth plugin: {object.hook_name}")
logging.info(f"Activated CustomAuth plugin from API: {object.spec.get('APIID')}")
elif object.hook_type == HookType.PostKeyAuth:
logging.info(f"PostKeyAuth plugin name: {object.hook_name}")
logging.info(f"Activated PostKeyAuth plugin from API: {object.spec.get('APIID')}")
elif object.hook_type == HookType.Post:
logging.info(f"Post plugin name: {object.hook_name}")
logging.info(f"Activated Post plugin from API: {object.spec.get('APIID')}")
elif object.hook_type == HookType.Response:
logging.info(f"Response plugin name: {object.hook_name}")
logging.info(f"Activated Response plugin from API: {object.spec.get('APIID')}")
logging.info("--------\n")
return object
Our Dispatch RPC method accepts the two parameters, object and context. The object parameter allows us to inspect the state and session of the request object dispatched by Tyk Gateway, via accessor methods. The context parameter can be used to set timeout limits etc. associated with the RPC call.
The important takeaways from the source code listing above are:
- The MessageToJson function is used to deserialize the request payload as JSON.
- In the context of custom plugins we access the hook_type and hook_name attributes of the Object message to determine which plugin to execute.
- The ID of the API associated with the request is accessible from the spec dictionary, object.spec.get(‘APIID’).
An implementation of the Dispatch RPC method must return the object payload received from Tyk Gateway. The payload can be modified by the service implementation, for example to add or remove headers and query parameters before the request is sent upstream.
DispatchEvent
Our implementation of the DispatchEvent RPC method will deserialize and output the event payload as JSON. Append the following source code to the async_server.py file:
async def DispatchEvent(
self, event: coprocess_object_pb2.Event, context: grpc.aio.ServicerContext
) -> coprocess_object_pb2.EventReply:
event = json.loads(event.payload)
http://logging.info (f"RECEIVED EVENT: {event}")
return coprocess_object_pb2.EventReply()
The DispatchEvent RPC method accepts the two parameters, event and context. The event parameter allows us to inspect the payload of the event dispatched by Tyk Gateway. The context parameter can be used to set timeout limits etc. associated with the RPC call.
The important takeaways from the source code listing above are:
- The event data is accessible from the payload attribute of the event parameter.
- An implementation of the DispatchEvent RPC method must return an instance of coprocess_object_pb2.EventReply.
Create gRPC Server
Finally, we will implement an AsyncIO gRPC server to handle requests from Tyk Gateway to the Dispatcher service. We will add functions to start and stop our gRPC server. Finally, we will use grpcurl to issue a test payload to our gRPC server to test that it is working.
Develop gRPC Server
Append the following source code from the listing below to the async_server.py file:
async def serve() -> None:
server = grpc.aio.server()
coprocess_object_pb2_grpc.add_DispatcherServicer_to_server(
PythonDispatcher(), server
)
listen_addr = "[::]:50051"
SERVICE_NAMES = (
coprocess_object_pb2.DESCRIPTOR.services_by_name["Dispatcher"].full_name,
reflection.SERVICE_NAME,
)
reflection.enable_server_reflection(SERVICE_NAMES, server)
server.add_insecure_port(listen_addr)
logging.info ("Starting server on %s", listen_addr)
await server.start()
await server.wait_for_termination()
async def shutdown_server(server) -> None:
http://logging.info ("Shutting down server...")
await server.stop(None)
The serve function starts the gRPC server, listening for requests on port 50051 with reflection enabled.
Clients can use reflection to list available services, obtain their RPC methods and retrieve their message types and field names dynamically. This is particularly useful for tooling and debugging purposes, allowing clients to discover server capabilities without prior knowledge of the service definitions.
note
A descriptor is a data structure that describes the structure of the messages, services, enums and other elements defined in a .proto file. The purpose of the descriptor is primarily metadata: it provides information about the types and services defined in the protocol buffer definition. The coprocess_object_pb2.py file that we generated using protoc contains a DESCRIPTOR field that we can use to retrieve this metadata. For further details consult the documentation for the Google’s protobuf FileDescriptor class.
The shutdown_server function stops the gRPC server via the stop method of the server instance.
The key takeaways from the source code listing above are:
- An instance of a gRPC server is created using grpc.aio.server().
- A service implementation should be registered with the gRPC server. We register our PythonDispatcher class via coprocess_object_pb2_grpc.add_DispatcherServicer_to_server(PythonDispatcher(), server).
- Reflection can be enabled to allow clients to dynamically discover the services available at a gRPC server. We enabled our Dispatcher service to be discovered via reflection.enable_server_reflection(SERVICE_NAMES, server). SERVICE_NAMES is a tuple containing the full names of two gRPC services: the Dispatcher service obtained by using the DESCRIPTOR field within the coprocess_object_pb2 module and the other being the standard reflection service.
- The server instance should be started via invoking and awaiting the start and wait_for_termination methods of the server instance.
- A port may be configured for the server. In this example we configured an insecure port of 50051 on the server instance via the add_insecure_port function. It is also possible to add a secure port via the add_secure_port method of the server instance, which accepts the port number in addition to an SSL certificate and key to enable TLS encryption.
- The server instance can be stopped via its stop method.
Finally, we will allow our server to terminate upon receipt of SIGTERM and SIGINT signals. To achieve this, append the source code listed below to the async_server.py file.
def handle_sigterm(sig, frame) -> None:
asyncio.create_task(shutdown_server(server))
async def handle_sigint() -> None:
loop = asyncio.get_running_loop()
for sig in (signal.SIGINT, signal.SIGTERM):
loop.add_signal_handler(sig, loop.stop)
if __name__ == '__main__':
logging.basicConfig(level=logging.INFO)
server = None
signal.signal(signal.SIGTERM, handle_sigterm)
try:
asyncio.get_event_loop().run_until_complete(serve())
except KeyboardInterrupt:
pass
Start gRPC Server
Issue the following command to start the gRPC server:
python3 -m async_server
A message should be output on the console, displaying the port number and confirming that the gRPC server has started.
Test gRPC Server
To test our gRPC server is working, issue test requests to the Dispatch and DispatchEvent methods, using grpcurl.
####### Send Dispatch Request
Use the grpcurl command to send a test dispatch request to our gRPC server:
grpcurl -plaintext -d '{
"hookType": "Pre",
"hookName": "MyPreCustomPluginForBasicAuth",
"request": {
"headers": {
"User-Agent": "curl/8.1.2",
"Host": "tyk-gateway.localhost:8080",
"Authorization": "Basic ZGV2QHR5ay5pbzpwYXN0cnk=",
"Accept": "*/*"
},
"url": "/basic-authentication-valid/get",
"returnOverrides": {
"responseCode": -1
},
"method": "GET",
"requestUri": "/basic-authentication-valid/get",
"scheme": "https"
},
"spec": {
"bundle_hash": "d41d8cd98f00b204e9800998ecf8427e",
"OrgID": "5e9d9544a1dcd60001d0ed20",
"APIID": "04e911d3012646d97fcdd6c846fafc4b"
}
}' localhost:50051 coprocess.Dispatcher/Dispatch
Inspect the console output of your gRPC server. It should echo the payload that you sent in the request.
####### Send DispatchEvent Request
Use the grpcurl command to send a test event payload to our gRPC server:
grpcurl -plaintext -d '{"payload": "{\"event\": \"test\"}"}' localhost:50051 coprocess.Dispatcher/DispatchEvent
Inspect the console output of your gRPC server. It should display a log similar to that shown below:
INFO:root:RECEIVED EVENT: {'event': 'test'}
The response received from the server should be an empty event reply, similar to that shown below:
grpcurl -plaintext -d '{"payload": "{\"event\": \"test\"}"}' localhost:50051 coprocess.Dispatcher/DispatchEvent
{}
At this point we have tested, independently of Tyk Gateway, that our gRPC Server can handle an example request payload for gRPC plugin execution. In the next section we will create a test environment for testing that Tyk Gateway integrates with our gRPC server for API requests.
Configure Test Environment
Now that we have implemented and started a gRPC server, Tyk Gateway needs to be configured to integrate with it. To achieve this we will enable the coprocess feature and configure the URL of the gRPC server.
We will also create an API so that we can test that Tyk Gateway integrates with our gRPC server.
Configure Tyk Gateway
Within the root of the tyk.conf file, add the following configuration, replacing host and port with values appropriate for your environment:
"coprocess_options": {
"enable_coprocess": true,
"coprocess_grpc_server": "tcp://host:port"
}
Alternatively, the following environment variables can be set in your .env file:
TYK_GW_COPROCESSOPTIONS_ENABLECOPROCESS=true
TYK_GW_COPROCESSOPTIONS_COPROCESSGRPCSERVER=tcp://host:port
Replace host and port with values appropriate for your environment.
Configure API
Before testing our gRPC server we will create and configure an API with 2 plugins:
- Pre Request: Named MyPreRequestPlugin.
- Response: Named MyResponsePlugin and configured so that Tyk Gateway dispatches the session state with the request.
Each plugin will be configured to use the grpc plugin driver.
Tyk Gateway will forward details of an incoming request to the gRPC server, for each of the configured API plugins.
####### Tyk Classic API
gRPC plugins can be configured within the custom_middleware section of the Tyk Classic ApiDefinition, as shown in the listing below:
{
"created_at": "2024-03-231T12:49:52Z",
"api_model": {},
"api_definition": {
...
...
"custom_middleware": {
"pre": [
{
"disabled": false,
"name": "MyPreRequestPlugin",
"path": "",
"require_session": false,
"raw_body_only": false
}
],
"post": [],
"post_key_auth": [],
"auth_check": {
"disabled": false,
"name": "",
"path": "",
"require_session": false,
"raw_body_only": false
},
"response": [
{
"disabled": false,
"name": "MyResponsePlugin",
"path": "",
"require_session": true,
"raw_body_only": false
}
],
"driver": "grpc",
"id_extractor": {
"disabled": false,
"extract_from": "",
"extract_with": "",
"extractor_config": {}
}
}
}
In the above listing, the plugin driver parameter has been configured with a value of grpc. Two plugins are configured within the custom_middleware section: a Pre Request plugin and a Response plugin.
The Response plugin is configured with require_session enabled, so that Tyk Gateway will send details for the authenticated key / user with the gRPC request. Note, this is not configured for Pre Request plugins that are triggered before authentication in the request lifecycle.
####### Tyk OAS API
To quickly get started, a Tyk OAS API schema can be created by importing the infamous pet store OAS schema. Then the findByStatus endpoint can be used for testing.
The resulting Tyk OAS API Definition contains the OAS JSON schema with an x-tyk-api-gateway section appended, as listed below. gRPC plugins can be configured within the middleware section of the x-tyk-api-gateway that is appended at the end of the OAS schema:
"x-tyk-api-gateway": {
"info": {
"id": "6e2ae9b858734ea37eb772c666517f55",
"dbId": "65f457804773a600011af41d",
"orgId": "5e9d9544a1dcd60001d0ed20",
"name": "Swagger Petstore - OpenAPI 3.0 Custom Authentication",
"state": {
"active": true
}
},
"upstream": {
"url": "https://petstore3.swagger.io/api/v3/"
},
"server": {
"listenPath": {
"value": "/custom_auth",
"strip": true
},
"authentication": {
"enabled": true,
"custom": {
"enabled": true,
"header": {
"enabled": false,
"name": "Authorization"
}
}
}
},
"middleware": {
"global": {
"pluginConfig": {
"driver": "grpc"
}
},
"cors": {
"enabled": false,
"maxAge": 24,
"allowedHeaders": [
"Accept",
"Content-Type",
"Origin",
"X-Requested-With",
"Authorization"
],
"allowedOrigins": [
"*"
],
"allowedMethods": [
"GET",
"HEAD",
"POST"
]
},
"prePlugin": {
"api-management/plugins/overview#": [
{
"enabled": true,
"functionName": "MyPreRequestPlugin",
"path": ""
}
]
},
"responsePlugin": {
"api-management/plugins/overview#": [
{
"enabled": true,
"functionName": "MyResponsePlugin",
"path": "",
"requireSession": true
}
]
}
}
}
In the above listing, the plugin driver parameter has been set to grpc. Two plugins are configured within the middleware section: a Pre Request plugin and a Response plugin.
The Response plugin is configured with requireSession enabled, so that Tyk Gateway will send details for the authenticated key / user with the gRPC request. Note, this is not configurable for Pre Request plugins that are triggered before authentication in the request lifecycle.
Tyk Gateway will forward details of an incoming request to the gRPC server, for each plugin.
Test API
We have implemented and configured a gRPC server to integrate with Tyk Gateway. Furthermore, we have created an API that has been configured with two gRPC plugins: a Pre Request and Response plugin.
When we issue a request to our API and observe the console output of our gRPC server we should see a JSON representation of the request headers etc. echoed in the terminal.
Issue a request for your API in the terminal window. For example:
curl -L http://.localhost:8080/grpc-http-bin
Observe the console output of your gRPC server. Tyk Gateway should have dispatched two requests to your gRPC server; a request for the Pre Request plugin and a request for the Response plugin.
The gRPC server we implemented echoes a JSON representation of the request payload dispatched by Tyk Gateway.
Note that this is a useful feature for learning how to develop gRPC plugins and understanding the structure of the request payload dispatched by Tyk Gateway to the gRPC server. However, in production environments care should be taken to avoid inadvertently exposing sensitive data such as secrets in the session.
Summary
In this guide, we’ve delved into the integration of a Python gRPC server with Tyk Gateway.
We have explained how to implement a Python gRPC server and equipped developers with the necessary tools, knowledge and capabilities to effectively utilize Tyk Gateway through gRPC services.
The following essential groundwork has been covered:
- Setting up tools, libraries and service definitions for the integration.
- Developing a basic gRPC server with functionality to echo the request payload, received from Tyk Gateway, in JSON format.
- Configuring Tyk Gateway for seamless communication with our gRPC server.
Create a Request Transformation Plugin with Java
This tutorial will guide you through the creation of a gRPC-based Java plugin for Tyk. Our plugin will inject a header into the request before it gets proxied upstream. For additional information about gRPC, check the official documentation here.
The sample code that we’ll use implements a request transformation plugin using Java and uses the proper gRPC bindings generated from our Protocol Buffers definition files.
Requirements
- Tyk Gateway: This can be installed using standard package management tools like Yum or APT, or from source code. See here for more installation options.
- The Tyk CLI utility, which is bundled with our RPM and DEB packages, and can be installed separately from https://github.com/TykTechnologies/tyk-cli.
- In Tyk 2.8 the Tyk CLI is part of the gateway binary, you can find more information by running “tyk help bundle”.
- Gradle Build Tool: https://gradle.org/install/.
- gRPC tools: https://grpc.io/docs/quickstart/csharp.html#generate-grpc-code
- Java JDK 7 or higher.
Create the Plugin
Setting up the Java Project
We will use the Gradle build tool to generate the initial files for our project:
cd ~
mkdir tyk-plugin
cd tyk-plugin
gradle init
We now have a tyk-plugin
directory containing the basic skeleton of our application.
Add the following to build.gradle
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.1'
}
}
plugins {
id "com.google.protobuf" version "0.8.1"
id "java"
id "application"
id "idea"
}
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:3.3.0"
}
plugins {
grpc {
artifact = 'io.grpc:protoc-gen-grpc-java:1.5.0'
}
}
generateProtoTasks {
all()*.plugins {
grpc {}
}
}
generatedFilesBaseDir = "$projectDir/src/generated"
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
mainClassName = "com.testorg.testplugin.PluginServer"
repositories {
mavenCentral()
}
dependencies {
compile 'io.grpc:grpc-all:1.5.0'
}
idea {
module {
sourceDirs += file("${projectDir}/src/generated/main/java");
sourceDirs += file("${projectDir}/src/generated/main/grpc");
}
}
Create the Directory for the Server Class
cd ~/tyk-plugin
mkdir -p src/main/java/com/testorg/testplugin
Install the gRPC Tools
We need to download the Tyk Protocol Buffers definition files, these files contains the data structures used by Tyk. See Data Structures for more information:
cd ~/tyk-plugin
git clone https://github.com/TykTechnologies/tyk
mv tyk/coprocess/proto src/main/proto
Generate the Bindings
To generate the Protocol Buffers bindings we use the Gradle build task:
gradle build
If you need to customize any setting related to the bindings generation step, check the build.gradle
file.
Implement Server
We need to implement two classes: one class will contain the request dispatcher logic and the actual middleware implementation. The other one will implement the gRPC server using our own dispatcher.
From the ~/tyk-plugin/src/main/java/com/testorg/testplugin
directory, create a file named PluginDispatcher.java
with the following code:
package com.testorg.testplugin;
import coprocess.DispatcherGrpc;
import coprocess.CoprocessObject;
public class PluginDispatcher extends DispatcherGrpc.DispatcherImplBase {
@Override
public void dispatch(CoprocessObject.Object request,
io.grpc.stub.StreamObserver<CoprocessObject.Object> responseObserver) {
CoprocessObject.Object modifiedRequest = null;
switch (request.getHookName()) {
case "MyPreMiddleware":
modifiedRequest = MyPreHook(request);
default:
// Do nothing, the hook name isn't implemented!
}
// Return the modified request (if the transformation was done):
if (modifiedRequest != null) {
responseObserver.onNext(modifiedRequest);
};
responseObserver.onCompleted();
}
CoprocessObject.Object MyPreHook(CoprocessObject.Object request) {
CoprocessObject.Object.Builder builder = request.toBuilder();
builder.getRequestBuilder().putSetHeaders("customheader", "customvalue");
return builder.build();
}
}
In the same directory, create a file named PluginServer.java
with the following code. This is the server implementation:
package com.testorg.testplugin;
import coprocess.DispatcherGrpc;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
public class PluginServer {
private static final Logger logger = Logger.getLogger(PluginServer.class.getName());
static Server server;
static int port = 5555;
public static void main(String[] args) throws IOException, InterruptedException {
System.out.println("Initializing gRPC server.");
// Our dispatcher is instantiated and attached to the server:
server = ServerBuilder.forPort(port)
.addService(new PluginDispatcher())
.build()
.start();
blockUntilShutdown();
}
static void blockUntilShutdown() throws InterruptedException {
if (server != null) {
server.awaitTermination();
}
}
}
To run the gRPC server we can use the following command:
cd ~/tyk-plugin
gradle runServer
The gRPC server will listen on port 5555 (as defined in Server.java
). In the next steps we’ll setup the plugin bundle and modify Tyk to connect to our gRPC server.
Bundle the Plugin
We need to create a manifest file within the tyk-plugin
directory. This file contains information about our plugin and how we expect it to interact with the API that will load it. This file should be named manifest.json
and needs to contain the following:
{
"custom_middleware": {
"driver": "grpc",
"pre": [{
"name": "MyPreMiddleware"
}]
}
}
- The
custom_middleware
block contains the middleware settings like the plugin driver we want to use (driver
) and the hooks that our plugin will expose. We use thepre
hook for this tutorial. For other hooks see here. - The
name
field references the name of the function that we implemented in our plugin code -MyPreMiddleware
. This will be handled by our dispatcher gRPC method inPluginServer.java
.
To bundle our plugin run the following command in the tyk-plugin
directory. Check your tyk-cli install path first:
/opt/tyk-gateway/utils/tyk-cli bundle build -y
For Tyk 2.8 use:
/opt/tyk-gateway/bin/tyk bundle build -y
A plugin bundle is a packaged version of the plugin. It may also contain a cryptographic signature of its contents. The -y
flag tells the Tyk CLI tool to skip the signing process in order to simplify the flow of this tutorial.
For more information on the Tyk CLI tool, see here.
You should now have a bundle.zip
file in the tyk-plugin
directory.
Publish the Plugin
To publish the plugin, copy or upload bundle.zip
to a local web server like Nginx, or Apache or storage like Amazon S3. For this tutorial we’ll assume you have a web server listening on localhost
and accessible through http://localhost
.
Configure Tyk
You will need to modify the Tyk global configuration file tyk.conf
to use gRPC plugins. The following block should be present in this file:
"coprocess_options": {
"enable_coprocess": true,
"coprocess_grpc_server": "tcp://localhost:5555"
},
"enable_bundle_downloader": true,
"bundle_base_url": "http://localhost/bundles/",
"public_key_path": ""
tyk.conf Options
enable_coprocess
: This enables the plugin.coprocess_grpc_server
: This is the URL of our gRPC server.enable_bundle_downloader
: This enables the bundle downloader.bundle_base_url
: This is a base URL that will be used to download the bundle. You should replace the bundle_base_url with the appropriate URL of the web server that’s serving your plugin bundles. For now HTTP and HTTPS are supported but we plan to add more options in the future (like pulling directly from S3 buckets).public_key_path
: Modifypublic_key_path
in case you want to enforce the cryptographic check of the plugin bundle signatures. If thepublic_key_path
isn’t set, the verification process will be skipped and unsigned plugin bundles will be loaded normally.
Configure an API Definition
There are two important parameters that we need to add or modify in the API definition.
The first one is custom_middleware_bundle
which must match the name of the plugin bundle file. If we keep this with the default name that the Tyk CLI tool uses, it will be bundle.zip
:
"custom_middleware_bundle": "bundle.zip"
Assuming the bundle_base_url
is http://localhost/bundles/
, Tyk will use the following URL to download our file:
http://localhost/bundles/bundle.zip
The second parameter is specific to this tutorial, and should be used in combination with use_keyless
to allow an API to authenticate against our plugin:
"use_keyless": false,
"enable_coprocess_auth": true
enable_coprocess_auth
will instruct the Tyk gateway to authenticate this API using the associated custom authentication function that’s implemented by our plugin.
Configuration via the Tyk Dashboard
To attach the plugin to an API, from the Advanced Options tab in the API Designer enter bundle.zip
in the Plugin Bundle ID field.
We also need to modify the authentication mechanism that’s used by the API. From the Core Settings tab in the API Designer select Use Custom Authentication (Python, CoProcess, and JSVM plugins) from the Target Details - Authentication Mode drop-down list.
Testing the Plugin
At this point we have our test HTTP server ready to serve the plugin bundle and the configuration with all the required parameters. The final step is to start or restart the Tyk Gateway (this may vary depending on how you set up Tyk):
service tyk-gateway start
A simple CURL request will be enough for testing our custom authentication middleware.
This request will trigger an authentication error:
curl http://localhost:8080/my-api/my-path -H 'Authorization: badtoken'
This will trigger a successful authentication. We’re using the token that’s specified in our server implementation (see line 57 in Server.cs
):
curl http://localhost:8080/my-api/my-path -H 'Authorization: abc123'
We also have a GitHub repository that includes tests and authentication middleware.
What’s Next?
In this tutorial we learned how Tyk gRPC plugins work. For a production-level setup we suggest the following:
- Configure an appropriate web server and path to serve your plugin bundles.
Create Custom Authentication Plugin with .NET
This tutorial will guide you through the creation of a custom authentication plugin for Tyk with a gRPC based plugin with .NET and C#. For additional information check the official gRPC documentation.
The sample code that we’ll use implements a very simple authentication layer using .NET and the proper gRPC bindings generated from our Protocol Buffers definition files.
Requirements
- Tyk Gateway: This can be installed using standard package management tools like Yum or APT, or from source code. See here for more installation options.
- The Tyk CLI utility, which is bundled with our RPM and DEB packages, and can be installed separately from https://github.com/TykTechnologies/tyk-cli
- In Tyk 2.8 the Tyk CLI is part of the gateway binary, you can find more information by running “tyk help bundle”.
- .NET Core for your OS: https://www.microsoft.com/net/core
- gRPC tools: https://grpc.io/docs/quickstart/csharp.html#generate-grpc-code
Create the Plugin
Create .NET Project
We use the .NET CLI tool to generate the initial files for our project:
cd ~
dotnet new console -o tyk-plugin
We now have a tyk-plugin
directory containing the basic skeleton of a .NET application.
From the tyk-plugin
directory we need to install a few packages that the gRPC server requires:
dotnet add package Grpc --version 1.6.0
dotnet add package System.Threading.ThreadPool --version 4.3.0
dotnet add package Google.Protobuf --version 3.4.0
- The
Grpc
package provides base code for our server implementation. - The
ThreadPool
package is used byGrpc
. - The
Protobuf
package will be used by our gRPC bindings.
Install the gRPC Tools
We need to install the gRPC tools to generate the bindings. We recommended you follow the official guide here: https://grpc.io/docs/quickstart/csharp.html#generate-grpc-code.
Run the following Commands (both MacOS and Linux):
cd ~/tyk-plugin
temp_dir=packages/Grpc.Tools.1.6.x/tmp
curl_url=https://www.nuget.org/api/v2/package/Grpc.Tools/
mkdir -p $temp_dir && cd $temp_dir && curl -sL $curl_url > tmp.zip; unzip tmp.zip && cd .. && cp -r tmp/tools . && rm -rf tmp && cd ../..
chmod -Rf +x packages/Grpc.Tools.1.6.x/tools/
Then run the following, depending on your OS:
MacOS (x64)
export GRPC_TOOLS=packages/Grpc.Tools.1.6.x/tools/macosx_x64
Linux (x64)
export GRPC_TOOLS=packages/Grpc.Tools.1.6.x/tools/linux_x64
The GRPC_TOOLS
environment variable will point to the appropriate GrpcTools path that matches our operating system and architecture. The last step is to export a variable for the protoc
program; this is the main program used to generate bindings:
export GRPC_PROTOC=$GRPC_TOOLS/protoc
Now that we can safely run protoc
, we can download the Tyk Protocol Buffers definition files. These files contain the data structures used by Tyk. See Data Structures for more information:
cd ~/tyk-plugin
git clone https://github.com/TykTechnologies/tyk
Generate the bindings
To generate the bindings, we create an empty directory and run the protoc
tool using the environment variable that was set before:
mkdir Coprocess
$GRPC_PROTOC -I=tyk/coprocess/proto --csharp_out=Coprocess --grpc_out=Coprocess --plugin=protoc-gen-grpc=$GRPC_TOOLS/grpc_csharp_plugin tyk/coprocess/proto/*.proto
Run the following command to check the binding directory:
ls Coprocess
The output will look like this:
CoprocessCommon.cs CoprocessObject.cs CoprocessReturnOverrides.cs
CoprocessMiniRequestObject.cs CoprocessObjectGrpc.cs CoprocessSessionState.cs
Implement Server
Create a file called Server.cs
.
Add the following code to Server.cs
.
using System;
using System.Threading.Tasks;
using Grpc.Core;
using Coprocess;
class DispatcherImpl : Dispatcher.DispatcherBase
{
public DispatcherImpl()
{
Console.WriteLine("Instantiating DispatcherImpl");
}
// The Dispatch method will be called by Tyk for every configured hook, we'll implement a very simple dispatcher here:
public override Task<Coprocess.Object> Dispatch(Coprocess.Object thisObject, ServerCallContext context)
{
// thisObject is the request object:
Console.WriteLine("Receiving object: " + thisObject.ToString());
// hook contains the hook name, this will be defined in our plugin bundle and the implementation will be a method in this class (DispatcherImpl), we'll look it up:
var hook = this.GetType().GetMethod(thisObject.HookName);
// If hook is null then a handler method for this hook isn't implemented, we'll log this anyway:
if (hook == null)
{
Console.WriteLine("Hook name: " + thisObject.HookName + " (not implemented!)");
// We return the unmodified request object, so that Tyk can proxy this in the normal way.
return Task.FromResult(thisObject);
};
// If there's a handler method, let's log it and proceed with our dispatch work:
Console.WriteLine("Hook name: " + thisObject.HookName + " (implemented)");
// This will dynamically invoke our hook method, and cast the returned object to the required Protocol Buffers data structure:
var output = hook.Invoke(this, new object[] { thisObject, context });
return (Task<Coprocess.Object>)output;
}
// MyPreMiddleware implements a PRE hook, it will be called before the request is proxied upstream and before the authentication step:
public Task<Coprocess.Object> MyPreMiddleware(Coprocess.Object thisObject, ServerCallContext context)
{
Console.WriteLine("Calling MyPreMiddleware.");
// We'll inject a header in this request:
thisObject.Request.SetHeaders["my-header"] = "my-value";
return Task.FromResult(thisObject);
}
// MyAuthCheck implements a custom authentication mechanism, it will initialize a session object if the token matches a certain value:
public Task<Coprocess.Object> MyAuthCheck(Coprocess.Object thisObject, ServerCallContext context)
{
// Request.Headers contains all the request headers, we retrieve the authorization token:
var token = thisObject.Request.Headers["Authorization"];
Console.WriteLine("Calling MyAuthCheck with token = " + token);
// We initialize a session object if the token matches "abc123":
if (token == "abc123")
{
Console.WriteLine("Successful auth!");
var session = new Coprocess.SessionState();
session.Rate = 1000;
session.Per = 10;
session.QuotaMax = 60;
session.QuotaRenews = 1479033599;
session.QuotaRemaining = 0;
session.QuotaRenewalRate = 120;
session.Expires = 1479033599;
session.LastUpdated = 1478033599.ToString();
thisObject.Metadata["token"] = token;
thisObject.Session = session;
return Task.FromResult(thisObject);
}
// If the token isn't "abc123", we return the request object in the original state, without a session object, Tyk will reject this request:
Console.WriteLine("Rejecting auth!");
return Task.FromResult(thisObject);
}
}
Create a file called Program.cs
to instantiate our dispatcher implementation and start a gRPC server.
Add the following code to Program.cs
.
using System;
using Grpc.Core;
namespace tyk_plugin
{
class Program
{
// Port to attach the gRPC server to:
const int Port = 5555;
static void Main(string[] args)
{
// We initialize a Grpc.Core.Server and attach our dispatcher implementation to it:
Server server = new Server
{
Services = { Coprocess.Dispatcher.BindService(new DispatcherImpl()) },
Ports = { new ServerPort("localhost", Port, ServerCredentials.Insecure) }
};
server.Start();
Console.WriteLine("gRPC server listening on " + Port);
Console.WriteLine("Press any key to stop the server...");
Console.ReadKey();
server.ShutdownAsync().Wait();
}
}
}
To run the gRPC server use the following command from the plugin directory:
dotnet run
The gRPC server will listen on port 5555 (as defined in Program.cs
). In the next steps we’ll setup the plugin bundle and modify Tyk to connect to our gRPC server.
Bundle the Plugin
We need to create a manifest file within the tyk-plugin
directory. This file contains information about our plugin and how we expect it to interact with the API that will load it. This file should be named manifest.json
and needs to contain the following:
{
"custom_middleware": {
"driver": "grpc",
"auth_check": {
"name": "MyAuthMiddleware",
"path": "",
"raw_body_only": false,
"require_session": false
}
}
}
- The
custom_middleware
block contains the middleware settings like the plugin driver we want to use (driver
) and the hooks that our plugin will expose. We use theauth_check
hook for this tutorial. For other hooks see here. - The
name
field references the name of the function that we implement in our plugin code -MyAuthMiddleware
. This will be handled by our dispatcher gRPC method (implemented inServer.cs
). - The
path
field is the path to the middleware component. - The
raw_body_only
field - The
require_session
field, if set totrue
gives you access to the session object. It will be supplied as a session variable to your middleware processor function
To bundle our plugin run the following command in the tyk-plugin
directory. Check your tyk-cli install path first:
/opt/tyk-gateway/utils/tyk-cli bundle build -y
From Tyk v2.8 upwards you can use:
/opt/tyk-gateway/bin/tyk bundle build -y
A plugin bundle is a packaged version of the plugin. It may also contain a cryptographic signature of its contents. The -y
flag tells the Tyk CLI tool to skip the signing process in order to simplify the flow of this tutorial.
For more information on the Tyk CLI tool, see here.
You should now have a bundle.zip
file in the tyk-plugin
directory.
Publish the Plugin
To publish the plugin, copy or upload bundle.zip
to a local web server like Nginx, or Apache or storage like Amazon S3. For this tutorial we’ll assume you have a web server listening on localhost
and accessible through http://localhost
.
Configure Tyk
You will need to modify the Tyk global configuration file tyk.conf
to use gRPC plugins. The following block should be present in this file:
"coprocess_options": {
"enable_coprocess": true,
"coprocess_grpc_server": "tcp://localhost:5555"
},
"enable_bundle_downloader": true,
"bundle_base_url": "http://localhost/bundles/",
"public_key_path": ""
tyk.conf Options
enable_coprocess
: This enables the plugin.coprocess_grpc_server
: This is the URL of our gRPC server.enable_bundle_downloader
: This enables the bundle downloader.bundle_base_url
: This is a base URL that will be used to download the bundle. You should replace the bundle_base_url with the appropriate URL of the web server that’s serving your plugin bundles. For now HTTP and HTTPS are supported but we plan to add more options in the future (like pulling directly from S3 buckets).public_key_path
: Modifypublic_key_path
in case you want to enforce the cryptographic check of the plugin bundle signatures. If thepublic_key_path
isn’t set, the verification process will be skipped and unsigned plugin bundles will be loaded normally.
Configure an API Definition
There are two important parameters that we need to add or modify in the API definition.
The first one is custom_middleware_bundle
which must match the name of the plugin bundle file. If we keep this with the default name that the Tyk CLI tool uses, it will be bundle.zip
:
"custom_middleware_bundle": "bundle.zip"
Assuming the bundle_base_url
is http://localhost/bundles/
, Tyk will use the following URL to download our file:
http://localhost/bundles/bundle.zip
The second parameter is specific to this tutorial, and should be used in combination with use_keyless
to allow an API to authenticate against our plugin:
"use_keyless": false,
"enable_coprocess_auth": true
enable_coprocess_auth
will instruct the Tyk gateway to authenticate this API using the associated custom authentication function that’s implemented by our plugin.
Configuration via the Tyk Dashboard
To attach the plugin to an API, from the Advanced Options tab in the API Designer enter bundle.zip
in the Plugin Bundle ID field.
We also need to modify the authentication mechanism that’s used by the API. From the Core Settings tab in the API Designer select Use Custom Authentication (Python, CoProcess, and JSVM plugins) from the Target Details - Authentication Mode drop-down list.
Testing the Plugin
At this point we have our test HTTP server ready to serve the plugin bundle and the configuration with all the required parameters. The final step is to start or restart the Tyk Gateway (this may vary depending on how you set up Tyk):
service tyk-gateway start
A simple CURL request will be enough for testing our custom authentication middleware.
This request will trigger an authentication error:
curl http://localhost:8080/my-api/my-path -H 'Authorization: badtoken'
This will trigger a successful authentication. We’re using the token that’s specified in our server implementation (see line 57 in Server.cs
):
curl http://localhost:8080/my-api/my-path -H 'Authorization: abc123'
We also have a GitHub repository that includes tests and authentication middleware.
What’s Next?
In this tutorial we learned how Tyk gRPC plugins work. For a production-level setup we suggest the following:
- Configure an appropriate web server and path to serve your plugin bundles.
- See the following GitHub repo for a gRPC based .NET plugin that incorporates authentication based on Microsoft SQL Server.
Create Custom Authentication Plugin with NodeJS
This tutorial will guide you through the creation of a custom authentication plugin for Tyk with a gRPC based plugin written in NodeJS. For additional information about gRPC, check the official documentation here.
The sample code that we’ll use implements a very simple authentication layer using NodeJS and the proper gRPC bindings generated from our Protocol Buffers definition files.
Requirements
- Tyk Gateway: This can be installed using standard package management tools like Yum or APT, or from source code. See here for more installation options.
- The Tyk CLI utility, which is bundled with our RPM and DEB packages, and can be installed separately from https://github.com/TykTechnologies/tyk-cli
- In Tyk 2.8 and upwards the Tyk CLI is part of the gateway binary, you can find more information by running “tyk help bundle”.
- NodeJS v6.x.x https://nodejs.org/en/download/
Create the Plugin
Create NodeJS Project
We will use the NPM tool to initialize our project, follow the steps provided by the init
command:
cd ~
mkdir tyk-plugin
cd tyk-plugin
npm init
Now we’ll add the gRPC package for this project:
npm install --save grpc
Install gRPC Tools
Typically to use gRPC and Protocol Buffers you need to use a code generator and generate bindings for the target language that you’re using. For this tutorial we’ll skip this step and use the dynamic loader that’s provided by the NodeJS gRPC library. This mechanism allows a program to load Protocol Buffers definitions directly from .proto
files. See this section in the gRPC documentation for more details.
To fetch the required .proto
files, you may use an official repository where we keep the Tyk Protocol Buffers definition files:
cd ~/tyk-plugin
git clone https://github.com/TykTechnologies/tyk
Implement Server
Now we’re ready to implement our gRPC server, create a file called main.js
in the project’s directory
Add the following code to main.js
.
const grpc = require('grpc'),
resolve = require('path').resolve
const tyk = grpc.load({
file: 'coprocess_object.proto',
root: resolve(__dirname, 'tyk/coprocess/proto')
}).coprocess
const listenAddr = '127.0.0.1:5555',
authHeader = 'Authorization'
validToken = '71f6ac3385ce284152a64208521c592b'
// The dispatch function is called for every hook:
const dispatch = (call, callback) => {
var obj = call.request
// We dispatch the request based on the hook name, we pass obj.request which is the coprocess.Object:
switch (obj.hook_name) {
case 'MyPreMiddleware':
preMiddleware(obj, callback)
break
case 'MyAuthMiddleware':
authMiddleware(obj, callback)
break
default:
callback(null, obj)
break
}
}
const preMiddleware = (obj, callback) => {
var req = obj.request
// req is the coprocess.MiniRequestObject, we inject a header using the "set_headers" field:
req.set_headers = {
'mycustomheader': 'mycustomvalue'
}
// Use this callback to finish the operation, sending back the modified object:
callback(null, obj)
}
const authMiddleware = (obj, callback) => {
var req = obj.request
// We take the value from the "Authorization" header:
var token = req.headers[authHeader]
// The token should be attached to the object metadata, this is used internally for key management:
obj.metadata = {
token: token
}
// If the request token doesn't match the "validToken" constant we return the call:
if (token != validToken) {
callback(null, obj)
return
}
// At this point the token is valid and a session state object is initialized and attached to the coprocess.Object:
var session = new tyk.SessionState()
session.id_extractor_deadline = Date.now() + 100000000000
obj.session = session
callback(null, obj)
}
main = function() {
server = new grpc.Server()
server.addService(tyk.Dispatcher.service, {
dispatch: dispatch
})
server.bind(listenAddr, grpc.ServerCredentials.createInsecure())
server.start()
}
main()
To run the gRPC server run:
node main.js
The gRPC server will listen on port 5555
(see the listenAddr
constant). In the next steps we’ll setup the plugin bundle and modify Tyk to connect to our gRPC server.
Bundle the Plugin
We need to create a manifest file within the tyk-plugin
directory. This file contains information about our plugin and how we expect it to interact with the API that will load it. This file should be named manifest.json
and needs to contain the following:
{
"custom_middleware": {
"driver": "grpc",
"auth_check": {
"name": "MyAuthMiddleware",
"path": "",
"raw_body_only": false,
"require_session": false
}
}
}
- The
custom_middleware
block contains the middleware settings like the plugin driver we want to use (driver
) and the hooks that our plugin will expose. We use theauth_check
hook for this tutorial. For other hooks see here. - The
name
field references the name of the function that we implement in our plugin code -MyAuthMiddleware
. The implemented dispatcher uses a switch statement to handle this hook, and calls theauthMiddleware
function inmain.js
. - The
path
field is the path to the middleware component. - The
raw_body_only
field - The
require_session
field, if set totrue
gives you access to the session object. It will be supplied as a session variable to your middleware processor function
To bundle our plugin run the following command in the tyk-plugin
directory. Check your tyk-cli install path first:
/opt/tyk-gateway/utils/tyk-cli bundle build -y
For Tyk 2.8 use:
/opt/tyk-gateway/bin/tyk bundle build -y
A plugin bundle is a packaged version of the plugin. It may also contain a cryptographic signature of its contents. The -y
flag tells the Tyk CLI tool to skip the signing process in order to simplify the flow of this tutorial.
For more information on the Tyk CLI tool, see here.
You should now have a bundle.zip
file in the tyk-plugin
directory.
Publish the Plugin
To publish the plugin, copy or upload bundle.zip
to a local web server like Nginx, Apache or storage like Amazon S3. For this tutorial we’ll assume you have a web server listening on localhost
and accessible through http://localhost
.
Configure Tyk
You will need to modify the Tyk global configuration file tyk.conf
to use gRPC plugins. The following block should be present in this file:
"coprocess_options": {
"enable_coprocess": true,
"coprocess_grpc_server": "tcp://localhost:5555"
},
"enable_bundle_downloader": true,
"bundle_base_url": "http://localhost/bundles/",
"public_key_path": ""
tyk.conf Options
enable_coprocess
: This enables the plugin.coprocess_grpc_server
: This is the URL of our gRPC server.enable_bundle_downloader
: This enables the bundle downloader.bundle_base_url
: This is a base URL that will be used to download the bundle. You should replace the bundle_base_url with the appropriate URL of the web server that’s serving your plugin bundles. For now HTTP and HTTPS are supported but we plan to add more options in the future (like pulling directly from S3 buckets).public_key_path
: Modifypublic_key_path
in case you want to enforce the cryptographic check of the plugin bundle signatures. If thepublic_key_path
isn’t set, the verification process will be skipped and unsigned plugin bundles will be loaded normally.
Configure an API Definition
There are two important parameters that we need to add or modify in the API definition.
The first one is custom_middleware_bundle
which must match the name of the plugin bundle file. If we keep this with the default name that the Tyk CLI tool uses, it will be bundle.zip
:
"custom_middleware_bundle": "bundle.zip"
Assuming the bundle_base_url
is http://localhost/bundles/
, Tyk will use the following URL to download our file:
http://localhost/bundles/bundle.zip
The second parameter is specific to this tutorial, and should be used in combination with use_keyless
to allow an API to authenticate against our plugin:
"use_keyless": false,
"enable_coprocess_auth": true
enable_coprocess_auth
will instruct the Tyk gateway to authenticate this API using the associated custom authentication function that’s implemented by our plugin.
Configuration via the Tyk Dashboard
To attach the plugin to an API, from the Advanced Options tab in the API Designer enter bundle.zip
in the Plugin Bundle ID field.
We also need to modify the authentication mechanism that’s used by the API. From the Core Settings tab in the API Designer select Use Custom Authentication (Python, CoProcess, and JSVM plugins) from the Target Details - Authentication Mode drop-down list.
Testing the Plugin
At this point we have our test HTTP server ready to serve the plugin bundle and the configuration with all the required parameters. The final step is to start or restart the Tyk Gateway (this may vary depending on how you set up Tyk):
service tyk-gateway start
A simple CURL request will be enough for testing our custom authentication middleware.
This request will trigger an authentication error:
curl http://localhost:8080/my-api/my-path -H 'Authorization: badtoken'
This will trigger a successful authentication. We’re using the token that’s specified in our server implementation (see line 57 in Server.cs
):
curl http://localhost:8080/my-api/my-path -H 'Authorization: abc123'
We also have a GitHub repository that includes tests and authentication middleware.
What’s Next?
In this tutorial we learned how Tyk gRPC plugins work. For a production-level setup we suggest the following:
- Configure an appropriate web server and path to serve your plugin bundles.
Create Custom Authentication Plugin With Python
In the realm of API security, HMAC-signed authentication serves as a foundational concept. In this developer-focused blog post, we’ll use HMAC-signed authentication as the basis for learning how to write gRPC custom authentication plugins with Tyk Gateway. Why learn how to write Custom Authentication Plugins?
- Foundational knowledge: Writing custom authentication plugins provides foundational knowledge of Tyk’s extensibility and customization capabilities.
- Practical experience: Gain hands-on experience in implementing custom authentication logic tailored to specific use cases, starting with HMAC-signed authentication.
- Enhanced control: Exercise greater control over authentication flows and response handling, empowering developers to implement advanced authentication mechanisms beyond built-in features.
While Tyk Gateway offers built-in support for HMAC-signed authentication, this tutorial serves as a practical guide for developers looking to extend Tyk’s capabilities through custom authentication plugins. It extends the gRPC server that we developed in our getting started guide.
We will develop a basic gRPC server that implements the Tyk Dispatcher service with a custom authentication plugin to handle authentication keys, signed using the HMAC SHA512 algorithm. Subsequently, you will be able to make a request to your API with a HMAC signed authentication key in the Authorization header. Tyk Gateway will intercept the request and forward it to your Python gRPC server for HMAC signature and token verification.
Our plugin will only verify the key against an expected value. In a production environment it will be necessary to verify the key against Redis storage.
Before we continue ensure that you have:
- Read and completed our getting started guide that explains how to implement a basic Python gRPC server to echo the request payload received from Tyk Gateway. This tutorial extends the source code of the tyk_async_server.py file to implement a custom authentication plugin for a HMAC signed authentication key.
- Read our HMAC signatures documentation for an explanation of HMAC signed authentication with Tyk Gateway. A brief summary is given in the HMAC Signed Authentication section below.
HMAC Signed Authentication
Before diving in further, we will give a brief overview of HMAC signed authentication using our custom authentication plugin.
- Client request: The journey begins with a client requesting access to a protected resource on the Tyk API.
- HMAC signing: Before dispatching the request, the client computes an HMAC signature using a secret key and request date, ensuring the payload’s integrity.
- Authorization header: The HMAC signature, along with essential metadata such as the API key and HMAC algorithm, is embedded within the Authorization header.
- Tyk Gateway verification: Upon receipt, Tyk Gateway forwards the request to our gRPC server to execute the custom authentication plugin. This will validate the HMAC signature, ensuring the request’s authenticity before proceeding with further processing.
Requests should be made to an API that uses our custom authentication plugin as follows. A HMAC signed key should be included in the Authorization header and a date/time string in the Date header. An example request is shown in the curl command below:
curl -v -H 'Date: Fri, 03 May 2024 12:00:42 GMT' \
-H 'Authorization: Signature keyId="eyJvcmciOiI1ZTlkOTU0NGExZGNkNjAwMDFkMGVkMjAiLCJpZCI6ImdycGNfaG1hY19rZXkiLCJoIjoibXVybXVyNjQifQ==", \
algorithm="hmac-sha512",signature="9kwBK%2FyrjbSHJDI7INAhBmhHLTHRDkIe2uRWHEP8bgQFQvfXRksm6t2MHeLUyk9oosWDZyC17AbGeP8EFqrp%2BA%3D%3D"' \
http://localhost:8080/grpc-custom-auth/get
From the above example, it should be noted that:
-
The Date header contains a date string formatted as follows: Fri, 03 May 2024 11:06:00 GMT.
-
The Authorization header is formatted as Signature keyId=”
”, algorithm=” where:”, signature=” ” - keyId is a Tyk authentication key.
- algorithm is the HMAC algorithm used to sign the signature, hmac-sha512 or hmac-sha256.
- signature is the HAMC signature calculated with the date string from the Date header, signed with a base64 encoded secret value, using the specified HMAC algorithm. The HMAC signature is then encoded as base64.
Prerequisites
Firstly, we need to create the following:
- An API configured to use a custom authentication plugin.
- A HMAC enabled key with a configured secret for signing.
This will enable us to issue a request to test that Tyk Gateway integrates with our custom authentication plugin on the gRPC server.
Create API
We will create an API served by Tyk Gateway, that will forward requests upstream to https://httpbin.org/.
The API will have the following parameters configured:
- Listen path: Tyk Gateway will listen to API requests on /grpc-custom-auth/ and will strip the listen path for upstream requests.
- Target URL: The target URL will be configured to send requests to http://httpbin/.
- Authentication Mode: The authentication mode will be configured for custom authentication. This is used to trigger CoProcess (gRPC), Python or JSVM plugins to handle custom authentication.
You can use the following Tyk Classic API definition to get you started, replacing the org_id with the ID of your organization.
{
"api_definition": {
"id": "662facb2f03e750001a03500",
"api_id": "6c56dd4d3ad942a94474df6097df67ed",
"org_id": "5e9d9544a1dcd60001d0ed20",
"name": "Python gRPC Custom Auth",
"enable_coprocess_auth": true,
"auth": {
"auth_header_name": "Authorization"
},
"proxy": {
"preserve_host_header": false,
"listen_path": "/grpc-custom-auth/",
"disable_strip_slash": true,
"strip_listen_path": true,
"target_url": "http://httpbin/"
},
"version_data": {
"not_versioned": false,
"versions": {
"Default": {
"name": "Default",
"expires": "",
"use_extended_paths": true,
"extended_paths": {
"ignored": [],
"white_list": [],
"black_list": []
}
}
},
"default_version": "Default"
},
"active": true
}
}
The Tyk API definition above can be imported via Tyk Dashboard. Alternatively, if using Tyk Gateway OSS, a POST request can be made to the api/apis endpoint of Tyk Gateway. Consult the Tyk Gateway Open API Specification documentation for usage.
An illustrative example using curl is given below. Please note that you will need to:
- Update the location to use the protocol scheme, host and port suitable for your environment.
- Replace the value in the x-tyk-authorization header with the secret value in your tyk.conf file.
- Replace the org_id with the ID of your organization.
curl -v \
--header 'Content-Type: application/json' \
--header 'x-tyk-authorization: your Gateway admin secret' \
--location http://localhost:8080/tyk/apis/ \
--data '{\
"api_definition": {\
"id": "662facb2f03e750001a03502",\
"api_id": "6c56dd4d3ad942a94474df6097df67ef",\
"org_id": "5e9d9544a1dcd60001d0ed20",\
"name": "Python gRPC Custom Auth",\
"enable_coprocess_auth": true,\
"auth": {\
"auth_header_name": "Authorization"\
},\
"proxy": {\
"preserve_host_header": false,\
"listen_path": "/grpc-custom-auth-error/",\
"disable_strip_slash": true,\
"strip_listen_path": true,\
"target_url": "http://httpbin/"\
},\
"version_data": {\
"not_versioned": false,\
"versions": {\
"Default": {\
"name": "Default",\
"expires": "",\
"use_extended_paths": true,\
"extended_paths": {\
"ignored": [],\
"white_list": [],\
"black_list": []\
}\
}\
},\
"default_version": "Default"\
},\
"active": true\
}\
}'
A response similar to that given below will be returned by Tyk Gateway:
{
"key": "f97b748fde734b099001ca15f0346dfe",
"status": "ok",
"action": "added"
}
Create HMAC Key
We will create an key configured to use HMAC signing, with a secret of secret. The key will configured to have access to our test API.
You can use the following configuration below, replacing the value of the org_id with the ID of your organization.
{
"quota_max": 1000,
"quota_renews": 1596929526,
"quota_remaining": 1000,
"quota_reset": 1596843126,
"quota_used": 0,
"org_id": "5e9d9544a1dcd60001d0ed20",
"access_rights": {
"662facb2f03e750001a03500": {
"api_id": "662facb2f03e750001a03500",
"api_name": "Python gRPC Custom Auth",
"versions": ["Default"],
"allowed_urls": [],
"limit": null,
"quota_max": 1000,
"quota_renews": 1596929526,
"quota_remaining": 1000,
"quota_reset": 1596843126,
"quota_used": 0,
"per": 1,
"expires": -1
}
},
"enable_detailed_recording": true,
"hmac_enabled": true,
"hmac_string": "secret",
"meta_data": {}
}
You can use Tyk Gateway’s API to create the key by issuing a POST request to the tyk/keys endpoint. Consult the Tyk Gateway Open API Specification documentation for usage.
An illustrative example using curl is given below. Please note that you will need to:
- Update the location to use the protocol scheme, host and port suitable for your environment.
- Replace the value in the x-tyk-authorization header with the secret value in your tyk.conf file.
Replace the org_id with the ID of your organization.
curl --location 'http://localhost:8080/tyk/keys/grpc_hmac_key' \
--header 'x-tyk-authorization: your Gateay admin secret' \
--header 'Content-Type: application/json' \
--data '{\
"alias": "grpc_hmac_key",\
"quota_max": 1000,\
"quota_renews": 1596929526,\
"quota_remaining": 1000,\
"quota_reset": 1596843126,\
"quota_used": 0,\
"org_id": "5e9d9544a1dcd60001d0ed20",\
"access_rights": {\
"662facb2f03e750001a03500": {\
"api_id": "662facb2f03e750001a03500",\
"api_name": "python-grpc-custom-auth",\
"versions": ["Default"],\
"allowed_urls": [],\
"limit": null,\
"quota_max": 1000,\
"quota_renews": 1596929526,\
"quota_remaining": 1000,\
"quota_reset": 1596843126,\
"quota_used": 0,\
"per": 1,\
"expires": -1\
}\
},\
"enable_detailed_recording": true,\
"hmac_enabled": true,\
"hmac_string": "secret",\
"meta_data": {}\
}\
'
A response similar to that given below should be returned by Tyk Gateway:
{
"key": "eyJvcmciOiI1ZTlkOTU0NGExZGNkNjAwMDFkMGVkMjAiLCJpZCI6ImdycGNfaG1hY19rZXkiLCJoIjoibXVybXVyNjQifQ==",
"status": "ok",
"action": "added",
"key_hash": "a72fcdc09caa86b5"
}
Note
Make a note of the key ID given in the response, since we will need this to test our API.
Implement Plugin
Our custom authentication plugin will perform the following tasks:
- Extract the Authorization and Date headers from the request object.
- Parse the Authorization header to extract the keyId, algorithm and signature attributes.
- Compute the HMAC signature using the specific algorithm and date included in the header.
- Verify that the computed HMAC signature matches the signature included in the Authorization header. A 401 error response will be returned if verification fails. Our plugin will only verify the key against an expected value. In a production environment it will be necessary to verify the key against Redis storage.
- Verify that the keyId matches an expected value (VALID_TOKEN). A 401 error response will be returned to Tyk Gateway if verification fails.
- If verification of the signature and key passes then update the session with HMAC enabled and set the HMAC secret. Furthermore, add the key to the Object metadata.
Return the request Object containing the updated session back to Tyk Gateway. When developing custom authentication plugins it is the responsibility of the developer to update the session state with the token, in addition to setting the appropriate response status code and error message when authentication fails.
Import Python Modules
Ensure that the following Python modules are imported at the top of your tyk_async_server.py file:
import asyncio
import base64
import hashlib
import hmac
import json
import re
import signal
import logging
import urllib.parse
import grpc
from google.protobuf.json_format import MessageToJson
from grpc_reflection.v1alpha import reflection
import coprocess_object_pb2_grpc
import coprocess_object_pb2
from coprocess_common_pb2 import HookType
from coprocess_session_state_pb2 import SessionState
Add Constants
Add the following constants to the top of the tyk_async_server.py file, after the import statements:
SECRET = "c2VjcmV0"
VALID_TOKEN = "eyJvcmciOiI1ZTlkOTU0NGExZGNkNjAwMDFkMGVkMjAiLCJpZCI6ImdycGNfaG1hY19rZXkiLCJoIjoibXVybXVyNjQifQ=="
- SECRET is a base64 representation of the secret used for HMAC signing.
- VALID_TOKEN is the key ID that we will authenticate against.
The values listed above are designed to align with the examples provided in the Prerequisites section, particularly those related to HMAC key generation. If you’ve made adjustments to the HMAC secret or you’ve modified the key alias referred to in the endpoint path (for instance, grpc_hmac_key), you’ll need to update these constants accordingly.
Extract headers
Add the following function to your tyk_async_server.py file to extract a dictionary of the key value pairs from the Authorization header. We will use a regular expression to extract the key value pairs.
def parse_auth_header(auth_header: str) -> dict[str,str]:
pattern = r'(\w+)\s*=\s*"([^"]+)"'
matches = re.findall(pattern, auth_header)
parsed_data = dict(matches)
return parsed_data
Compute HMAC Signature
Add the following function to your tyk_async_server.py to compute the HMAC signature.
def generate_hmac_signature(algorithm: str, date_string: str, secret_key: str) -> str:
if algorithm == "hmac-sha256":
hash_algorithm = hashlib.sha256
elif algorithm == "hmac-sha512":
hash_algorithm = hashlib.sha512
else:
raise ValueError("Unsupported hash algorithm")
base_string = f"date: {date_string}"
logging.info(f"generating signature from: {base_string}")
hmac_signature = hmac.new(secret_key.encode(), base_string.encode(), hash_algorithm)
return base64.b64encode(hmac_signature.digest()).decode()
Our function accepts three parameters:
- algorithm is the HMAC algorithm to use for signing. We will use HMAC SHA256 or HMAC SHA512 in our custom authentication plugin
- date_string is the date extracted from the date header in the request sent by Tyk Gateway.
- secret_key is the value of the secret used for signing.
The function computes and returns the HMAC signature for a string formatted as date: date_string, where date_string corresponds to the value of the date_string parameter. The signature is computed using the secret value given in the secret_key parameter and the HMAC algorithm given in the algorithm parameter. A ValueError is raised if the hash algorithm is unrecognized.
We use the following Python modules in our implementation:
- hmac Python module to compute the HMAC signature.
- base64 Python module to encode the result.
Verify HMAC Signature
Add the following function to your tyk_async_server.py file to verify the HMAC signature provided by the client:
def verify_hmac_signature(algorithm: str, signature: str, source_string) -> bool:
expected_signature = generate_hmac_signature(algorithm, source_string, SECRET)
received_signature = urllib.parse.unquote(signature)
if expected_signature != received_signature:
error = f"Signatures did not match\nreceived: {received_signature}\nexpected: {expected_signature}"
logging.error(error)
else:
logging.info("Signatures matched!")
return expected_signature == received_signature
Our function accepts three parameters:
- algorithm is the HMAC algorithm to use for signing. We will use hmac-sha256 or hmac-sha512 in our custom authentication plugin.
- signature is the signature string extracted from the Authorization header.
- source_string is the date extracted from the date header in the request sent by Tyk Gateway.
- secret_key is the value of the secret used for signing.
The function calls generate_hmac_signature to verify the signatures match. It returns true if the computed and client HMAC signatures match, otherwise false is returned.
Set Error Response
Add the following helper function to tyk_async_server.py to allow us to set the response status and error message if authentication fails.
def set_response_error(object: coprocess_object_pb2.Object, code: int, message: str) -> None:
object.request.return_overrides.response_code = code
object.request.return_overrides.response_error = message
Our function accepts the following three parameters:
- object is an instance of the Object message representing the payload sent by Tyk Gateway to the Dispatcher service in our gRPC server. For further details of the payload structure dispatched by Tyk Gateway to a gRPC server please consult our gRPC documentation.
- code is the HTTP status code to return in the response.
- message is the response message.
The function modifies the return_overrides attribute of the request, updating the response status code and error message. The return_overrides attribute is an instance of a ReturnOverrides message that can be used to override the response of a given HTTP request. When this attribute is modified the request is terminated and is not sent upstream.
Authenticate
Add the following to your tyk_async_server.py file to implement the main custom authentication function. This parses the headers to extract the signature and date from the request, in addition to verifying the HMAC signature and key:
def authenticate(object: coprocess_object_pb2.Object) -> coprocess_object_pb2.Object:
keys_to_check = ["keyId", "algorithm", "signature"]
auth_header = object.request.headers.get("Authorization")
date_header = object.request.headers.get("Date")
parse_dict = parse_auth_header(auth_header)
if not all(key in parse_dict for key in keys_to_check) or not all([auth_header, date_header]):
set_response_error(object, 400, "Custom middleware: Bad request")
return object
try:
signature_valid = verify_hmac_signature(
parse_dict["algorithm"],
parse_dict["signature"],
date_header
)
except ValueError:
set_response_error(object, 400, "Bad HMAC request, unsupported algorithm")
return object
if not signature_valid or parse_dict["keyId"] != VALID_TOKEN:
set_response_error(object, 401, "Custom middleware: Not authorized")
else:
new_session = SessionState()
new_session.hmac_enabled = True
new_session.hmac_secret = SECRET
object.metadata["token"] = VALID_TOKEN
object.session.CopyFrom(new_session)
return object
The Object payload received from the Gateway is updated and returned as a response from the Dispatcher service:
- If authentication fails then we set the error message and status code for the response accordingly, using our set_response_error function.
- If authentication passes then we update the session attribute in the Object payload to indicate that HMAC verification was performed and provide the secret used for signing. We also add the verified key to the meta data of the request payload.
Specifically, our function performs the following tasks:
-
Extracts the Date and Authorization headers from the request and verifies that the Authorization header is structured correctly, using our parse_auth_header function. We store the extracted Authorization header fields in the parse_dict dictionary. If the structure is invalid then a 400 bad request response is returned to Tyk Gateway, using our set_response_error function.
-
We use our verify_hmac_signature function to compute and verify the HMAC signature. A 400 bad request error is returned to the Gateway if HMAC signature verification fails, due to an unrecognized HMAC algorithm.
-
A 401 unauthorized error response is returned to the Gateway under the following conditions:
- The client HMAC signature and the computed HMAC signature do not match.
- The extracted key ID does not match the expected key value in VALID_TOKEN.
-
If HMAC signature verification passed and the key included in the Authorization header is valid then we update the SessionState instance to indicate that HMAC signature verification is enabled, i.e. hmac_enabled is set to true. We also specify the HMAC secret used for signing in the hmac_secret field and include the valid token in the metadata dictionary.
Integrate Plugin
Update the Dispatch method of the PythonDispatcher class in your tyk_async_server.py file so that our authenticate function is called when the a request is made by Tyk Gateway to execute a custom authentication (HookType.CustomKeyCheck) plugin.
class PythonDispatcher(coprocess_object_pb2_grpc.DispatcherServicer):
async def Dispatch(
self, object: coprocess_object_pb2.Object, context: grpc.aio.ServicerContext
) -> coprocess_object_pb2.Object:
logging.info(f"STATE for {object.hook_name}\n{MessageToJson(object)}\n")
if object.hook_type == HookType.Pre:
logging.info(f"Pre plugin name: {object.hook_name}")
logging.info(f"Activated Pre Request plugin from API: {object.spec.get('APIID')}")
elif object.hook_type == HookType.CustomKeyCheck:
logging.info(f"CustomAuth plugin: {object.hook_name}")
logging.info(f"Activated CustomAuth plugin from API: {object.spec.get('APIID')}")
authenticate(object)
elif object.hook_type == HookType.PostKeyAuth:
logging.info(f"PostKeyAuth plugin name: {object.hook_name}")
logging.info(f"Activated PostKeyAuth plugin from API: {object.spec.get('APIID')}")
elif object.hook_type == HookType.Post:
logging.info(f"Post plugin name: {object.hook_name}")
logging.info(f"Activated Post plugin from API: {object.spec.get('APIID')}")
elif object.hook_type == HookType.Response:
logging.info(f"Response plugin name: {object.hook_name}")
logging.info(f"Activated Response plugin from API: {object.spec.get('APIID')}")
logging.info("--------\n")
return object
Test Plugin
Create the following bash script, hmac.sh, to issue a test request to an API served by Tyk Gateway. The script computes a HMAC signature and constructs the Authorization and Date headers for a specified API. The Authorization header contains the HMAC signature and key for authentication.
Replace the following constant values with values suitable for your environment:
- KEY represents the key ID for the HMAC signed key that you created at the beginning of this guide.
- HMAC_SECRET represents the base64 encoded value of the secret for your HMAC key that you created at the beginning of this guide.
- BASE_URL represents the base URL, containing the protocol scheme, host and port number that Tyk Gateway listens to for API requests.
- ENDPOINT represents the path of your API that uses HMAC signed authentication.
#!/bin/bash
BASE_URL=http://localhost:8080
ENDPOINT=/grpc-custom-auth/get
HMAC_ALGORITHM=hmac-sha512
HMAC_SECRET=c2VjcmV0
KEY=eyJvcmciOiI1ZTlkOTU0NGExZGNkNjAwMDFkMGVkMjAiLCJpZCI6ImdycGNfaG1hY19rZXkiLCJoIjoibXVybXVyNjQifQ==
REQUEST_URL=${BASE_URL}${ENDPOINT}
function urlencode() {
echo -n "$1" | perl -MURI::Escape -ne 'print uri_escape($_)' | sed "s/%20/+/g"
}
# Set date in expected format
date="$(LC_ALL=C date -u +"%a, %d %b %Y %H:%M:%S GMT")"
# Generate the signature using hmac algorithm with hmac secret from created Tyk key and
# then base64 encoded
signature=$(echo -n "date: ${date}" | openssl sha512 -binary -hmac "${HMAC_SECRET}" | base64)
# Ensure the signature is base64 encoded
url_encoded_signature=$(echo -n "${signature}" | perl -MURI::Escape -ne 'print uri_escape($_)' | sed "s/%20/+/g")
# Output the date, encoded date, signature and the url encoded signature
echo "request: ${REQUEST_URL}"
echo "date: $date"
echo "signature: $signature"
echo "url_encoded_signature: $url_encoded_signature"
# Make the curl request using headers
printf "\n\n----\n\nMaking request to http://localhost:8080/grpc-custom-auth/get\n\n"
set -x
curl -v -H "Date: ${date}" \
-H "Authorization: Signature keyId=\"${KEY}\",algorithm=\"${HMAC_ALGORITHM}\",signature=\"${url_encoded_signature}\"" \
${REQUEST_URL}
After creating and saving the script, ensure that it is executable by issuing the following command:
chmod +x hmac.sh
Issue a test request by running the script:
./hmac.sh
Observe the output of your gRPC server. You should see the request payload appear in the console output for the server and your custom authentication plugin should have been triggered. An illustrative example is given below:
2024-05-13 12:53:49 INFO:root:STATE for CustomHMACCheck
2024-05-13 12:53:49 {
2024-05-13 12:53:49 "hookType": "CustomKeyCheck",
2024-05-13 12:53:49 "hookName": "CustomHMACCheck",
2024-05-13 12:53:49 "request": {
2024-05-13 12:53:49 "headers": {
2024-05-13 12:53:49 "User-Agent": "curl/8.1.2",
2024-05-13 12:53:49 "Date": "Mon, 13 May 2024 11:53:49 GMT",
2024-05-13 12:53:49 "Host": "localhost:8080",
2024-05-13 12:53:49 "Authorization": "Signature keyId=\"eyJvcmciOiI1ZTlkOTU0NGExZGNkNjAwMDFkMGVkMjAiLCJpZCI6ImdycGNfaG1hY19rZXkiLCJoIjoibXVybXVyNjQifQ==\",algorithm=\"hmac-sha512\",signature=\"e9OiifnTDgi3PW2EGJWfeQXCuhuhi6bGLiGhUTFpjEfgdKmX%2FQOFrePAQ%2FAoSFGU%2FzpP%2FCabmQi4zQDPdRh%2FZg%3D%3D\"",
2024-05-13 12:53:49 "Accept": "*/*"
2024-05-13 12:53:49 },
2024-05-13 12:53:49 "url": "/grpc-custom-auth/get",
2024-05-13 12:53:49 "returnOverrides": {
2024-05-13 12:53:49 "responseCode": -1
2024-05-13 12:53:49 },
2024-05-13 12:53:49 "method": "GET",
2024-05-13 12:53:49 "requestUri": "/grpc-custom-auth/get",
2024-05-13 12:53:49 "scheme": "http"
2024-05-13 12:53:49 },
2024-05-13 12:53:49 "spec": {
2024-05-13 12:53:49 "bundle_hash": "d41d8cd98f00b204e9800998ecf8427e",
2024-05-13 12:53:49 "OrgID": "5e9d9544a1dcd60001d0ed20",
2024-05-13 12:53:49 "APIID": "6c56dd4d3ad942a94474df6097df67ed"
2024-05-13 12:53:49 }
2024-05-13 12:53:49 }
2024-05-13 12:53:49
2024-05-13 12:53:49 INFO:root:CustomAuth plugin: CustomHMACCheck
2024-05-13 12:53:49 INFO:root:Activated CustomAuth plugin from API: 6c56dd4d3ad942a94474df6097df67ed
2024-05-13 12:53:49 INFO:root:generating signature from: date: Mon, 13 May 2024 11:53:49 GMT
2024-05-13 12:53:49 INFO:root:Signatures matched!
2024-05-13 12:53:49 INFO:root:--------
Try changing the SECRET and/or KEY constants with invalid values and observe the output of your gRPC server. You should notice that authentication fails. An illustrative example is given below:
2024-05-13 12:56:37 INFO:root:STATE for CustomHMACCheck
2024-05-13 12:56:37 {
2024-05-13 12:56:37 "hookType": "CustomKeyCheck",
2024-05-13 12:56:37 "hookName": "CustomHMACCheck",
2024-05-13 12:56:37 "request": {
2024-05-13 12:56:37 "headers": {
2024-05-13 12:56:37 "User-Agent": "curl/8.1.2",
2024-05-13 12:56:37 "Date": "Mon, 13 May 2024 11:56:37 GMT",
2024-05-13 12:56:37 "Host": "localhost:8080",
2024-05-13 12:56:37 "Authorization": "Signature keyId=\"eyJvcmciOiI1ZTlkOTU0NGExZGNkNjAwMDFkMGVkMjAiLCJpZCI6ImdycGNfaG1hY19rZXkiLCJoIjoibXVybXVyNjQifQ==\",algorithm=\"hmac-sha512\",signature=\"KXhkWOS01nbxuFfK7wEBggkydXlKJswxbukiplboJ2n%2BU6JiYOil%2Bx4OE4edWipg4EcG9T49nvY%2Fc9G0XFJcfg%3D%3D\"",
2024-05-13 12:56:37 "Accept": "*/*"
2024-05-13 12:56:37 },
2024-05-13 12:56:37 "url": "/grpc-custom-auth/get",
2024-05-13 12:56:37 "returnOverrides": {
2024-05-13 12:56:37 "responseCode": -1
2024-05-13 12:56:37 },
2024-05-13 12:56:37 "method": "GET",
2024-05-13 12:56:37 "requestUri": "/grpc-custom-auth/get",
2024-05-13 12:56:37 "scheme": "http"
2024-05-13 12:56:37 },
2024-05-13 12:56:37 "spec": {
2024-05-13 12:56:37 "bundle_hash": "d41d8cd98f00b204e9800998ecf8427e",
2024-05-13 12:56:37 "OrgID": "5e9d9544a1dcd60001d0ed20",
2024-05-13 12:56:37 "APIID": "6c56dd4d3ad942a94474df6097df67ed"
2024-05-13 12:56:37 }
2024-05-13 12:56:37 }
2024-05-13 12:56:37
2024-05-13 12:56:37 INFO:root:CustomAuth plugin: CustomHMACCheck
2024-05-13 12:56:37 INFO:root:Activated CustomAuth plugin from API: 6c56dd4d3ad942a94474df6097df67ed
2024-05-13 12:56:37 INFO:root:generating signature from: date: Mon, 13 May 2024 11:56:37 GMT
2024-05-13 12:56:37 ERROR:root:Signatures did not match
2024-05-13 12:56:37 received: KXhkWOS01nbxuFfK7wEBggkydXlKJswxbukiplboJ2n+U6JiYOil+x4OE4edWipg4EcG9T49nvY/c9G0XFJcfg==
2024-05-13 12:56:37 expected: zT17C2tgDCYBJCgFFN/mknf6XydPaV98a5gMPNUHYxZyYwYedIPIhyDRQsMF9GTVFe8khCB1FhfyhpmzrUR2Lw==
Summary
In this guide, we’ve explained how to write a Python gRPC custom authentication plugin for Tyk Gateway, using HMAC-signed authentication as a practical example. Through clear instructions and code examples, we’ve provided developers with insights into the process of creating custom authentication logic tailored to their specific API authentication needs.
While Tyk Gateway already supports HMAC-signed authentication out of the box, this guide goes beyond basic implementation by demonstrating how to extend its capabilities through custom plugins. By focusing on HMAC-signed authentication, developers have gained valuable experience in crafting custom authentication mechanisms that can be adapted and expanded to meet diverse authentication requirements.
It’s important to note that the authentication mechanism implemented in this guide solely verifies the HMAC signature’s validity and does not include access control checks against specific API resources. Developers should enhance this implementation by integrating access control logic to ensure authenticated requests have appropriate access permissions.
By mastering the techniques outlined in this guide, developers are better equipped to address complex authentication challenges and build robust API security architectures using Tyk Gateway’s extensibility features. This guide serves as a foundation for further exploration and experimentation with custom authentication plugins, empowering developers to innovate and customize API authentication solutions according to their unique requirements.
Performance
These are some benchmarks performed on gRPC plugins.
gRPC plugins may use different transports, we’ve tested TCP and Unix Sockets.
TCP
Unix Socket
Using Lua
Overview
Requirements
Tyk uses LuaJIT. The main requirement is the LuaJIT shared library, you may find this as libluajit-x
in most distros.
For Ubuntu 14.04 you may use:
$ apt-get install libluajit-5.1-2 $ apt-get install luarocks
The LuaJIT required modules are as follows:
- lua-cjson: in case you have
luarocks
, run:$ luarocks install lua-cjson
How to write LuaJIT Plugins
We have a demo plugin hosted in the repo tyk-plugin-demo-lua. The project implements a simple middleware for header injection, using a Pre hook (see Tyk custom middleware hooks) and mymiddleware.lua.
Lua Performance
Lua support is currently in beta stage. We are planning performance optimizations for future releases.
Tyk Lua API Methods
Tyk Lua API methods aren’t currently supported.
Lua Plugin Tutorial
Settings in the API Definition
To add a Lua plugin to your API, you must specify the bundle name using the custom_middleware_bundle
field:
{
"name": "Tyk Test API",
"api_id": "1",
"org_id": "default",
"definition": {
"location": "header",
"key": "version"
},
"auth": {
"auth_header_name": "authorization"
},
"use_keyless": true,
"version_data": {
"not_versioned": true,
"versions": {
"Default": {
"name": "Default",
"expires": "3000-01-02 15:04",
"use_extended_paths": true,
"extended_paths": {
"ignored": [],
"white_list": [],
"black_list": []
}
}
}
},
"proxy": {
"listen_path": "/quickstart/",
"target_url": "http://httpbin.org",
"strip_listen_path": true
},
"custom_middleware_bundle": "test-bundle",
}
Global settings
To enable Lua plugins you need to add the following block to tyk.conf
:
"coprocess_options": {
"enable_coprocess": true,
},
"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.
enable_bundle_downloader
enables the bundle downloader.
bundle_base_url
is 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.
Running the Tyk Lua build
To use Tyk with Lua support you will need to use an alternative binary, it is provided in the standard Tyk package but it has a different service name.
Firstly stop the standard Tyk version:
service tyk-gateway stop
and then start the Lua build:
service tyk-gateway-lua start