This document is a logical reasoning that takes elements from our meetings, a brainstorming that evolves from the beginnign to the end. Don't make any assumptions until you reach the end. Because the argument is very complex, it incrementally address the whole picture step by step.
This Gist comes from the realization that plugin mappings can also be adapted to consumers, and that grouping and filters would be available for both consumers and plugin configurations.
It specifically covers the topic of plugin and consumer mapping from the user's perspective, thus the interface and not the internals.
Let's suppose that by default every consumer can consume any API:
POST /consumers/
name="thefosk"
And then restrict the consumer to a specific API:
POST /mappings/
api="mockbin"
consumers=["thefosk"]
Or add the consumer to a group, and restrict the group to a specific API:
POST /groups/
name="admin_users"
consumers=["thefosk"]
POST /mappings/
api="mockbin"
groups=["admin_users"]
Let's also suppose that by default a plugin applies to any API:
POST /plugins/
id="plugin_configuration_1"
name="key-auth"
config.keynames=["apikey"]
Restrict the plugin to a specific API:
POST /mappings/
api="mockbin"
plugins=["plugin_configuration_1"]
Or add the plugin to a group, and apply the group to a specific API:
POST /groups/
name="internal_apis_configuration"
plugins=["plugin_configuration_1"]
POST /mappings/
api="mockbin"
groups=["internal_apis_configuration"]
Let's suppose we want to restrict the consumer to a specific API with a specific method, we could extend the previous request to:
POST /mappings/
api="mockbin"
consumers=["thefosk"]
methods=["GET"]
And let's also say we want to restrict the consumer to a specific API with a specific method:
POST /mappings/
api="mockbin"
consumers=["thefosk", "sinzone"]
plugins=["plugin_configuration_1"]
methods=["GET"]
Uhm? Does the above mean that only those consumers can consume the API, or does it mean the plugin should apply for those consumers? Maybe this is a solution, to divide the mappings in two types:
- ACLS
- Plugins
So that it becomes:
POST /mappings/plugins/
api="mockbin"
plugins=["plugin_configuration_1"]
consumers=["thefosk", "sinzone"]
methods=["GET"]
I want to divide my users in two groups, those who can make only GET
requests and those who can make both POST
and GET
requests, and the users who can use both methods also have a different rate limiting:
Create the consumers:
POST /consumers/
name="thefosk"
POST /consumers/
name="sinzone"
Add consumers to only_get
and get_and_post
groups:
POST /groups/
name="only_get"
consumers=["thefosk"]
POST /groups/
name="get_and_post"
consumers=["sinzone"]
Add the mappings:
POST /mappings/acls/
api="mockbin"
groups=["only_get"]
methods=["GET"]
POST /mappings/acls/
api="mockbin"
groups=["get_and_post"]
methods=["GET", "POST"]
Now let's configure the rate-limiting plugin:
POST /plugins/
id="plugin_configuration_1"
name="rate-limiting"
config.seconds=10
POST /mappings/plugins/
api="mockbin"
plugins=["plugin_configuration_1"]
Add add a special rate-limiting for those who can use both HTTP methods:
POST /plugins/
id="plugin_configuration_2"
name="rate-limiting"
config.seconds=1000
POST /mappings/plugins/
api="mockbin"
groups=["get_and_post"]
plugins=["plugin_configuration_2"]
This will work, but....
We can hide the complexity almost under the hood for 99% of users from the interface, while still keeping the complex routes for the 1% that needs to do crazy things:
The following code does exactly the same things of the previous chapter.
Create consumers and automatically associate them to a group in one request. If the group doesn't exist, the system will create it:
POST /consumers/
name="thefosk"
groups=["only_get"]
POST /consumers/
name="sinzone"
groups=["get_and_post"]
Add the HTTP method restrictions for the mockbin
API:
POST /apis/mockbin/acls/
groups=["only_get"]
methods=["GET"]
POST /apis/mockbin/acls/
groups=["get_and_post"]
methods=["GET", "POST"]
Create the rate-limiting plugin configurations, and associate them to a group if specified:
POST /apis/mockbin/plugins/
name="rate-limiting"
config.seconds=10
POST /apis/mockbin/plugins/
name="rate-limiting"
groups=["get_and_post"]
config.seconds=1000
Tadam! This will work.
They can still use the mapping API explicitly at the following endpoints:
/groups/
/mappings/acls/
/mappings/plugins/
or, if they know the API:
/apis/{api}/acls/
/apis/{api}/plugins/
Let's introduce filters. A filter can be an object with the following properties:
locations=["us", "fr"]
hosts=["host.com"]
paths=["/hello", "/world"]
ips=["1.1.1.1","2.2.2.1/24"]
methods=["GET", "POST"]
And much more in the future. At least one property needs to be specified.
A filter can be associated to a mapping, for example let's create this filter:
POST /filters/
name="north_america"
locations=["us", "ca"]
methods=["GET", "POST"]
and associate it to a mapping:
POST /apis/mockbin/acls/
groups=["get_and_post"]
filters=["north_america"]
The above operations allow the get_and_post
consumer group to make GET
and POST
requests only from USA and Canada.
Filters are not routes because they don't necessarily specify a route, and their list of properties can grow over time (for example we could filter requests coming from a specific range of IPs and a host, etc).
Here is how the final extreme use-case will look like with filters.
Create consumers and automatically associate them to a group in one request. If the group doesn't exist, the system will create it:
POST /consumers/
name="thefosk"
groups=["only_get"]
POST /consumers/
name="sinzone"
groups=["get_and_post"]
Create the filter restrictions:
POST /filters/
name="only_get_filter"
methods=["GET"]
POST /filters/
name="get_and_post_filter"
methods=["GET", "POST"]
Add the HTTP method restrictions for the mockbin
API:
POST /apis/mockbin/acls/
groups=["only_get"]
filters=["only_get_filter"]
POST /apis/mockbin/acls/
groups=["get_and_post"]
filters=["get_and_post_filter"]
Create the rate-limiting plugin configurations, and associate them to a group if specified:
POST /apis/mockbin/plugins/
name="rate-limiting"
config.seconds=10
POST /apis/mockbin/plugins/
name="rate-limiting"
groups=["get_and_post"]
config.seconds=1000
Filters are optional in the interface, they only make sense if they need to be re-used multiple times for multiple APIs.
Doing this:
POST /filters/
name="only_get_filter"
methods=["GET"]
POST /apis/mockbin/acls/
groups=["only_get"]
filters=["only_get_filter"]
could be the same as doing:
POST /apis/mockbin/acls/
groups=["only_get"]
methods=["GET"]
A filter can also filter incoming requests to detect the API to load:
POST /filters/
name="north_america"
locations=["us", "ca"]
methods=["GET", "POST"]
and can be attached to an API object:
POST /apis/
name="mockbin"
filters=["north_america"]
Every GET
or POST
request coming from US
or CA
will be proxied to mockbin
.
Endpoints available:
/groups/
/filters/
/mappings/acls/
/mappings/plugins/
or, if they know the API:
/apis/{api}/acls/
/apis/{api}/plugins/
Extreme example:
POST /consumers/
name="thefosk"
groups=["only_get"]
POST /consumers/
name="sinzone"
groups=["get_and_post"]
Add the HTTP method restrictions for the mockbin
API (under the hood we are creating Filters - or a Filter can be explicitly set using the filters
parameter):
POST /apis/mockbin/acls/
groups=["only_get"]
methods=["GET"]
POST /apis/mockbin/acls/
groups=["get_and_post"]
methods=["GET", "POST"]
Create the rate-limiting plugin configurations, and associate them to a group if specified:
POST /apis/mockbin/plugins/
name="rate-limiting"
config.seconds=10
POST /apis/mockbin/plugins/
name="rate-limiting"
groups=["get_and_post"]
config.seconds=1000
The end.