Configuration
Api Gateway adheres to vertx-server configuration schema (vertx-server configuration) In short, the input configuration is meta-config.json file that defines configuration sources (e.g. file, Consul, etc.).
The actual configuration has two main parts: Rules and application settings (HTTP server options, etc.). Sample configuration has following structure:
{
"orchis": {
"app": {
"port": 7773,
"rules": "$ref:rules",
"server": {
"http": {
"host": "localhost",
"ssl": false
}
},
"docsEnabled": true
}
},
"registry:request-plugins": {
},
"registry:response-plugins": {
},
"registry:sd": {
"sd": { "main": "io.orchis.tools.vertx.sd.SdVerticle" }
},
"registry:system": {
"fixed-sd-provider": { "main": "io.orchis.tools.vertx.sd.provider.FixedSdProvider" }
},
"circuit-breakers": {
"off": true
},
"fixed-sd-provider": {
"records": [
{
"name": "authz",
"location": {
"host": "localhost",
"port": 7777,
"ssl": false
}
}
]
},
"rules": []
}
You can configure HTTP server (e.g. host, ssl and certificates, etc.) at orchis.app.server.http
configuration path.
orchis.app.server.http
populates io.vertx.core.http.HttpServerOptions. See Vertx configuration for details.
Set orchis.app.port
to configure Api Gateway port.
Set orchis.app.alivePath
to configure alive path. /alive
by default.
Proxy headers
By default proxy headers are modified in each request to target service. If you want to disable it then set orchis.app.proxyHeaders.enabled
to false
in the config.
If enabled then following headers modification applies to requets:
-
Always add
remote-address.host
toX-Forwarded-For
headers -
Always add
remote-address.protocol
toX-Forwarded-Proto
headers -
If
Host
header is set add it toX-Forwarded-Host
headers -
If True Client IP header is missing then set it to first
X-Forwarded-For
value. True Client IP header name is read from configuration atorchis.app.proxyHeaders.inputTrueClientIp
, defaultX-Real-IP
-
Outgoing True Client IP header name is read from configuration at
orchis.app.proxyHeaders.outputTrueClientIp
, defaultX-Real-IP
Authn context in Access Logs
In order to log identity of the user in Access Logs you can configure orchis.app.accessLog.authnCtx
to specify what attributes from flow context
should be logged.
E.g.:
{
"authnCtx": {
"method": "authnMethod",
"user": "userUuid",
"device": "deviceUuid",
"uid": "session.uid"
}
}
Above configuration can log following authentication context: {"method":"sso","user":"4b1b17f8-a934-458f-3c08-cc01d9f9b917","uid":"admin@cloudentity.com"}
See AuthnPlugin for available objects and attributes.
Request headers in Access Logs
In order to log request headers in Access Logs you can configure orchis.app.accessLog.request.headers
to specify what headers should be logged.
If you want all headers to be logged set all
attribue to true
:
{
"request": {
"headers": {
"all": true
}
}
If you want to select specific headers then set whitelist
attribute:
{
"request": {
"headers": {
"whitelist": ["User-Agent"]
}
}
Splitting rules configuration
If you have many Rules it gets cumbersome to manage them in single configuration file. rules
attribute supports two formats: list or map.
List contains objects defining Rule as described in Rules. Map contains key-value pairs with Rule objects as values.
E.g.
{
...
"rules": {
"service-a": [
],
"service-b": [
]
}
...
}
Lists at service-a
and service-b
are concatenated. The order of concatenation is undefined.
In order to split Rules into multiple configuration files use folder
or classpath-folder
configuration store described in Vertx Tools docs.
Set outputPath
in configuration store config to rules
and put a list of Rules to each file in the path
folder.
E.g. config directory structure:
-
/config
-
/rules
-
service-a.json
-
service-b.json
[
{
"default": ...
"endpoints": ...
}
]
Logs
Access logs
Access logs have following JSON format:
{
"timestamp": "2018-04-06T15:14:33.929Z",
"trueClientIp": "192.168.0.13",
"remoteClient": "192.168.0.127",
"correlationIds": {
"local": "c6bf4bde-a568-487a-9b6c-1c0f2ecdd2ef",
"external0": "a8489baa-9dec-43f9-b8b0-1a632ef8e2f8"
},
"http": {
"httpVersion": "HTTP/1.1",
"method": "GET",
"uri": "/service-a/user/123",
"status": "200"
},
"gateway": {
"method": "GET",
"path": "/user/{userId}",
"pathPrefix": "/service-a",
"aborted": false,
"targetService": "service-a"
},
"authnCtx": {
"method": "sso",
"user":"4b1b17f8-a934-458f-3c08-cc01d9f9b917",
"uid":"admin@cloudentity.com"
}
},
"timeMs": "3"
}
Field | Description |
---|---|
datetime |
Request time in ISO 8601 format. |
trueClientIp |
IP address of original client, either X-Real-IP header or first IP from X-Forwarded-For or remote address. See Proxy headers. |
remoteIp |
IP of the direct client. |
correlationIds |
Correlation ids. 'local' ID is generated by API Gateway for internal logging only. 'external{num}' is added by Correlation ID plugin with order defined in Rule config. |
authnCtxSignature |
Identity of the user. See [Authentication context in Access Logs]. |
httpVersion |
HTTP version. |
method |
HTTP method. |
uri |
URI without host. |
status |
HTTP status code. |
gateway.method |
Method of matching Rule |
gateway.path |
Path pattern of matching Rule |
gateway.pathPrefix |
Path prefix of matching Rule |
gateway.aborted |
True if API Gateway aborted the call without proxying to target service. False otherwise. |
gateway.targetService |
Target service of matching Rule |
timeMs |
Time from receiving the request body till writing full response body. |
Plugin access logs
Each plugin can attach its own items to access log. See Plugins for details.
Target service namespace
In MicroPerimeter 2.0 deployment, access logs can contain namespace of the target service.
To enable it orchis.app.accessLog.targetServiceNamespace
configuration attribute must be set to true
.
Rules
Overview
Gateway Rules control how the API Gateway handles incoming requests. A Rule has 3 components: request matching criteria, target service configuration and plugins transforming request/response. The Rules configuration file contains a list of configuration blocks. We can think of a configuration block as a configuration per target service. Each block contains configuration of default values, a list of endpoint specific values and service plugins. If some of endpoint specific values are missing, they are picked up from default ones. When we apply default and endpoint configuration values we get final Rule configuration. Each configuration block is compiled into a list of rules. All block rules are merged into a final rules list.
The order of Rule definitions matters. The first Rule that the request match its criteria is picked up by the API Gateway and the corresponding plugin transformations are applied. Eventually the target service is called, unless some request plugin returned API response, thus aborting the call to the target service.
Rule’s attributes
Attribute | Component | Description |
---|---|---|
endpointName |
N/A |
Optional name for logging and inspection |
method |
Matching Criteria |
HTTP method |
pathPattern |
Matching Criteria |
Regular expression, prepended with |
rewritePath |
RewritePath |
Replace matching GW Uri pathPattern with value of rewritePath. It is possible to rewrite matching path with any count of params. |
copyQueryOnRewrite |
RewritePath |
Copy query parameters to target request when rewriting path. Optional, default true |
pathPrefix |
Matching Criteria |
Request path prefix, optional, defaults to empty string |
dropPrefix |
Target Service |
boolean, if true the path prefix of API request is dropped when proxying the call to target service, optional, defaults to true |
targetHost |
Target Service |
Target service host |
targetPort |
Target Service |
Target service port |
targetService |
Target Service |
Target service name used for Service Discovery |
requestPlugins |
Transformations |
List of configuration objects defining transformation of request to target service. See Endpoint plugins |
responsePlugins |
Transformations |
List of configuration objects defining transformation of response from target service. See Endpoint plugins |
Path pattern regular expression
Path pattern is a regular expression with some caveats.
-
Instead of trying to match a variable in path segment, we can use variable matcher. Simply, instead of path segment regular expression
[^/]+
use a variable name surrounded by curly braces{
and}
. E.g. instead of/user/[^/]+/session
path pattern we can use/user/{user_id}/session
. -
The path pattern matches whole API request path, i.e. the API Gateway is wrapping the path pattern with
^
and$
.
Rule configuration
The rule configuration has following structure:
[
{
"default": {
"targetHost": "my.service.com",
"targetPort": 8080,
"pathPrefix": "/my_service",
"dropPrefix": true
},
"endpoints": [
{
"method": "GET",
"pathPattern": "/static/.*"
}
]
}
]
In the example above, all GET requests incoming to API Gateway matching ^/my_service/static/.*$
regular expression are proxied to my.service.com
host at port 8080.
dropPrefix
attribute is set to true, so the pathPrefix
is dropped from the target path.
For example, /my_service/static/styles/main.css
is proxied to my.service.com:8080/static/styles/main.css
dropPrefix
is optional, it defaults to true
. pathPrefix
is also optional, it defaults to empty string.
Default values
If we have several endpoints sharing the same configuration we can move it to default
attribute.
If endpoint configuration attribute is missing it’s picked up from default
.
Path parameters
[
{
...
"endpoints": [
{
"method": "GET",
"pathPattern": "/items/{item_id}/status"
}
]
}
]
In order to match path parameter we can use '{paramater-name}' matcher.
Rewriting path
[
{
...
"endpoints": [
{
"method": "GET",
"pathPattern": "/items/{item_id}/status"
"rewritePath": "/entity/item/{item_id}/status"
}
]
}
]
It is possible to re-write the target path of the request. To do so, set rewritePath
attribute. You can reference path parameters
defined in pathPattern
.
For example, GET /items/1234/status
is transformed into GET /entity/item/1234/status
call to target service.
Endpoint plugins
You can apply extra logic to request or response using plugins.
Plugin configuration has two attributes name
and conf
:
{
"name": "plugin-a",
"conf": {
"param1": "value1"
}
}
Refer to Plugins for available plugins and their configuration.
For example, let’s apply plugin-a
to endpoint request:
[
{
...
"endpoints": [
{
"method": "GET",
"pathPattern": "/items/{item_id}/status",
"requestPlugins": [
{
"name": "plugin_a",
"conf": {
"param1": "value1"
}
}
]
}
]
}
]
Plugins are configured in an array. They are applied sequentially.
If you want to apply plugin to response, then configure it in responsePlugins
attribute:
[
{
...
"endpoints": [
{
"method": "GET",
"pathPattern": "/items/{item_id}/status",
"responsePlugins": [
{
"name": "plugin-x",
"conf": {
...
}
}
]
}
]
}
]
Service plugins
It is common case to apply a plugin to request/response of all endpoints in a service.
There are preFlow
, endpoint
and postFlow
plugins.
The following API Gateway flow diagram shows when the plugins are applied to request.
If you want to apply a plugin before endpoint plugins then configure it in request.preFlow.plugins
attribute:
[
{
...
"request": {
"preFlow": {
"plugins": [
{
"name": "plugin-a",
"conf": { ... }
}
]
}
},
"endpoints": [
{
"method": "GET",
"pathPattern": "/items/{item_id}/status",
"requestPlugins": [
{
"name": "plugin-b",
"conf": { ... }
}
]
}
]
}
]
In the example above, plugin-a
is applied for all endpoints in preFlow
phase.
In case of /items/{item_id}/status endpoint, first plugin-a
is applied and second plugin-b
.
If you want to apply a plugin after endpoint plugins then configure it in request.postFlow.plugins
attribute:
[
{
...
"request": {
"postFlow": {
"plugins": [
{
"name": "plugin-c",
"conf": { ... }
}
]
}
},
"endpoints": [
{
"method": "GET",
"pathPattern": "/items/{item_id}/status",
"requestPlugins": [
{
"name": "plugin-b",
"conf": { ... }
}
]
}
]
}
]
In the example above, plugin-c
is applied for all endpoints in postFlow
phase.
In case of /items/{item_id}/status endpoint, first plugin-b
is applied and second plugin-c
.
Similarly, you can define preFlow
and postFlow
plugins for response of all endpoints. Define response
attribute like below:
[
{
...
"response": {
"preFlow": {
"plugins": [
{
"name": "plugin-x",
"conf": { ... }
}
]
},
"postFlow": {
"plugins": [
{
"name": "plugin-y",
"conf": { ... }
}
]
}
},
"endpoints": [
...
]
}
]
Let’s cover preFlow
, endpoint
and postFlow
plugins in one configuration:
[
{
...
"request": {
"preFlow": {
"plugins": [
{
"name": "req-pre-1",
"conf": { ... }
},
{
"name": "req-pre-2",
"conf": { ... }
}
]
},
"postFlow": {
"plugins": [
{
"name": "req-post-1",
"conf": { ... }
}
]
}
},
"response": {
"preFlow": {
"plugins": [
{
"name": "resp-pre-1",
"conf": { ... }
}
]
},
"postFlow": {
"plugins": [
{
"name": "resp-post-1",
"conf": { ... }
}
]
}
},
"endpoints": [
{
"method": "GET",
"pathPattern": "/items/{item_id}/status",
"requestPlugins": [
{
"name": "req-end-1",
"conf": { ... }
}
],
"responsePlugins": [
{
"name": "resp-end-1",
"conf": { ... }
}
]
}
]
}
]
Following diagram shows the order in which plugins are applied:
Disabling preFlow/postFlow plugins
It is possible to disable preFlow
or postFlow
plugins for specific endpoint.
In order to disable all preFlow
or "postFlow" plugins you should set disableAllPlugins
to true.
See example, only authn
and authz
plugins are applied:
[
{
...
"request": {
"preFlow": {
"plugins": [
{
"name": "authn",
"conf": {
"methods": "sso"
}
}
]
},
"postFlow": {
"plugins": [
{
"name": "outgoingDefaultJwt",
"conf": {}
}
]
}
},
"endpoints": [
{
"method": "GET",
"pathPattern": "/items/{item_id}/status",
"request": {
"postFlow": {
"disableAllPlugins": true
}
}
"requestPlugins": [
{
"name": "authz",
"conf": {
"policy": "GET_ITEM"
}
}
]
}
]
}
]
In order to disable only some preFlow
or postFlow
plugins we can define their names in disablePlugins
attribute.
Let’s disable outgoingDefaultJwt
postFlow
plugin:
[
{
...
"request": {
"preFlow": {
"plugins": [
{
"name": "authn",
"conf": {
"methods": "sso"
}
}
]
},
"postFlow": {
"plugins": [
{
"name": "outgoingDefaultJwt",
"conf": {}
}
]
}
},
"endpoints": [
{
"method": "GET",
"pathPattern": "/items/{item_id}/status",
"request": {
"postFlow": {
"disablePlugins": ["outgoingDefaultJwt"]
}
}
"requestPlugins": [
{
"name": "authz",
"conf": {
"policy": "GET_ITEM"
}
}
]
}
]
}
]
Example
Given the following rules configuration file:
[
{
"default": {
"targetHost": "my.service.com",
"targetPort": 8080,
"pathPrefix": "/my_service",
"dropPrefix": true,
"requestPlugins": []
},
"endpoints": [
{
"method": "GET",
"pathPattern": "/static/.*"
},
{
"method": "GET",
"pathPattern": "/items"
},
{
"method": "GET",
"pathPattern": "/items/{item_id}"
},
{
"method": "GET",
"pathPattern": "/items/to/rewrite{item_id}",
"rewritePath": "/service/{item_id}"
},
{
"method": "POST",
"pathPattern": "/items",
"requestPlugins": [
{
"name": "authz",
"conf": {
"policy": "CREATE_ITEM"
}
}
]
}
]
}
]
we get a list of following rules:
# | method | pathPattern | rewritePath | pathPrefix | targetHost | targetPort | requestPlugins |
---|---|---|---|---|---|---|---|
1 |
GET |
|
N/A |
|
my.service.com |
8080 |
[] |
2 |
GET |
|
N/A |
|
my.service.com |
8080 |
[] |
3 |
GET |
|
N/A |
|
my.service.com |
8080 |
[] |
4 |
GET |
|
|
|
my.service.com |
8080 |
[] |
5 |
POST |
|
N/A |
|
my.service.com |
8080 |
|
Let’s follow step by step what happens when different requests hit API Gateway.
Request matching a Rule with a request plugin and reaching target service
request.method = 'GET' request.path = '/my_service/items'
-
Find a Rule where
Rule.method
equalsrequest.method
andrequest.path
matches regular expression built by concatenatingRule.pathPrefix
andRule.pathPattern
.-
The Rule #5 is found.
-
-
Apply request plugins.
-
The authz plugin is applied, checking that the requestor is authorized to perform the call.
-
-
Call target service.
-
Calling POST
http://my.service.com:8080/items
with body and headers copied from the original request. Note the/my_service
prefix was dropped. The target service responds.
-
-
Apply response plugins.
-
No response plugins is applied.
-
-
Return the final response.
Request not matching any Rules
request.method = 'PUT' request.path = '/my_service/items/1234'
-
Find a Rule.
-
There is no matching Rules. The API Gateway returns error response.
-
Request matching rule with rewriting path option
request.method = 'GET' request.path = '/my_service/items/to/rewrite/1234'
-
Find a Rule where
Rule.method
equalsrequest.method
andrequest.path
matches regular expression built by concatenatingRule.pathPrefix
andRule.pathPattern
.-
The Rule #4 is found.
-
Call target service.
-
Calling POST
http://my.service.com:8080/service/1234
with body and headers copied from the original request. Note the/my_service
prefix is not taken into account. The target service responds.
-
-
Apply response plugins.
-
No response plugins is applied.
-
-
Return the final response.
Plugins
AuthnPlugin
AuthnPlugin
performs authentication and puts extra entities in the request flow context.
AuthnPlugin
works in two steps. The first step performs authentication using one of allowed providers. This step returns
all entity UUIDs that could be identified (e.g. userUuid
, deviceUuid
, applicationUuid
) and some custom data (e.g. session
, token
).
The second step uses UUIDs and custom data from previous step to fetch required entity objects (e.g. user
, device
).
On authentication step it is possible to modify target request or API response, e.g. remove cookie with expired SSO token.
Example
[{
"default": { (...) },
"endpoints": [{
"method": "POST",
"pathPattern": "/path-with-policy",
"requestPlugins": [
{
"name": "authn",
"conf": {
"methods": ["sso", "authorizationCodeOAuth"],
"entities": ["user"]
}
}
]
}]
}]
In this example sso
and authorizationCodeOAuth
are allowed authentication methods. If authentication succeeds the user
object is fetched and put into request flow context.
Authentication methods
sso
Authenticates Cloudentity session. Gets session from session-service using SSO token. Token is read from header or cookie.
Header: ${conf.tokenHeaderName} Cookie: ${conf.tokenCookieName}
authnMethod = "sso" token = "1234" userUuid = "abd28djf" deviceUuid = "abd28djf" // optional, taken from session.deviceUuid session = {"uuid": "abd28djf", "name": "John Doe", "customer": "829hff4"} customerId = "829hff4" // taken from session.customer
{ "sessionService": { "ssl": false, "host": "local.orchis.syntegrity.com", "path": "/overlay/api", "port": 7080, "timeout": 3000, "debug": false }, "tokenHeaderName": "token", "tokenCookieName": "token", "userIdName": "uuid", "jwtServiceAddress": "symmetric" }
tokenHeaderName
and tokenCookieName
are optional. However, at least one of them should be defined.
If one of them is missing then the token is not read from header or cookie respectively.
authorizationCodeOAuth
Decodes OAuth access token using JWKSet from OIDC provider.
Header: Authorization, pattern: 'Bearer {accessToken}'
authnMethod = "authorizationCodeOAuth" userUuid = "abd28djf"
N/A
jwt
Header: Authorization, pattern: 'Bearer {jwt}'
jwt.claims
{ "jwtServiceAddress": "symmetric" }
jwt
authentication provider uses JWT service for parsing. jwtServiceAddress
config attribute defines what JWT service is used.
Usually it is symmetric
or asymmetric
, but other services with custom configuration can be used.
See <<JWT services>> for JWT configuration.
oauth10
Authenticates OAuth 1.0 request using oauth_consumer_key credentials: https://tools.ietf.org/html/rfc5849#section-3.4 and Authorization Header Parameter Transmission: https://tools.ietf.org/html/rfc5849#section-3.5.1
Flow:
-
parse authorization header
-
validate version
-
validate signature method
-
validate timestamp
-
validate nonce
-
validate signature
-
load key based on oauth_consumer_key
-
normalize request (calculate base string)
-
verify signature (recalculate signature and compare to provided oauth_signature)
-
Header: Authorization, pattern: 'OAuth {params}'
Authorization: OAuth oauth_consumer_key="clientId!keyId",oauth_nonce="d3265174678942d1a9deedc9e2bbc532",oauth_signature="q5uJYSRoUZ21lZzxunuHvbrWM%2FhnLQYyb0M66PknGe1v8OJYKT%2FAErqbEhOE%2BabvAvbojkC9Pk52qCA27x9X4Ttugjv%2FZ27YIRJymUJYVfDoyJbICqJub0TbQcOrLmiPhbD%2BQvv%2F8v2YmzH91RksWIn40226N5cenweaCkPvHpGnrY1fM81MjCwUXez8NoFkdyE%2BTjRWU5SSKv55%2BR6NjX8%2ByBYGLJeo3EjZjw0uzYhzNByvktVMN7SCqXo7Flohf7CBuRCmmb36zelRvan3f6ttg3ETsHrEMhx8vvNxGeTR8gbra7wo6vyn4yAaXF2DsDnmA8XcMawNg6wgPe3EXQ%3D%3D",oauth_signature_method="RSA-SHA256",oauth_timestamp="1532508191",oauth_version="1.0"
authnMethod = "oauth10"
{ "header": { "name": "Authorization", "valueRegexp": "OAuth (.*)" }, "publicDomain": { "host": "example.com", "port": 443, "ssl": true }, "skewTimeInSeconds": 30, "signingKeyLoader": "io.orchis.gateway.plugin.impl.authn.methods.oauth10.loader.ConfigRsaSigningKeyLoader", "signatureVerifiers": { "RSA-SHA256": "io.orchis.gateway.plugin.impl.authn.methods.oauth10.signer.RsaPublicKeySignatureVerifier" } }
header
section (required) configures header name
and valueRegexp
where oauth params are placed in.
publicDomain
(required) defines public interface where api-gateway is deployed e.g LB address.
skewTimeInSeconds
(optional, default: 15) allows to configure timestamp difference between client and server.
signingKeyLoader
(optional) defines verticle class responsible for key loading. By default ConfigRsaSigningKeyLoader is used which loads rsa public keys from config file.
Note
|
ConfigRsaSigningKeyLoader configuration
{ "publicKeys": { "key1": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5eXYjPCWbBCqu26V3SkUb7zbC7lrZwVr/fo58RBD5dHP9oBoEoP7cZKmIa1z3HRFNFnevb2f1xHn/pqVbcfSY2R0px7QeCPkjdcTR5E2MaZtZW7Y97jdoqUzth/wsu0e3AMkGvxtRUkH51J25b5XkYincbk9vy+6C1ApxNvOxbdlkcIGodEXcnEZ3SVdH01mAg/D0WhaT026nlajroTMatPYT7hdmp0JphakEx4XXqfY0ViaftRgArsHtE0tjm5MgcLWAEagl6tQPMV7qZjj/2VTiXim3mxIrhJzM/4PeVaPR18UnQaV/ezm0QJ9qQLZJUh217HaSF3KvQ10SrUQUwIDAQAB" } }
|
nonceValidator
(optional) defines verticle class responsible for nonce validation. By default HazelcastNonceValidator is used which checks
if combination of oauth_consumer_key + "." + oauth_timestamp + "." + oauth_nonce
hasn’t been used before.
Note
|
HazelcastNonceValidator configuration
{ "nonceTtlInSeconds": "15" } `nonceTtlInSeconds` configures hazelcast ttl after which entry is removed. |
consumerKeyRegex
(optional) defines regex where first group is value of key used to sign request. Default value is passthrough.
In case of some OAuth10 providers oauth_consumer_key has the following format: clientId!keyId
and to get consumer key following regex can be used:
"consumerKeyRegex": "\\!(.*)",
signatureVerifiers
(optional) defines a map of supported signature methods and corresponding verticle classes which handles signature verification.
By default RsaPublicKeySignatureVerifier is used.
Note
|
There is no need to manually put key loader / signature verticles in vertex registry, authn provider deploys them automatically on startup. |
Entity providers
For each authentication method you can define entity provider. Currently the authentication method - entity provider mapping looks like this:
sso: deviceUuid, userUuid, user, device authorizationCodeOAuth: userUuid, user
user
Loads Cloudentity User by userUuid
.
TargetRequest.ctx: userUuid
user = {"uuid": "abd28djf", "name": "John Doe"}
{ "userService": { "serviceName": "userService", "httpClientOptions": { "timeout": 3000 }, "circuitBreakerOptions": { "off": true }, "retries": 0 }, "jwtServiceAddress": "symmetric" }
device
Loads Cloudentity Device by deviceUuid
.
TargetRequest.ctx: deviceUuid
device = {"uuid": "dsoij3f4", ... }
{ "deviceService": { "serviceName": "deviceService", "httpClientOptions": { "timeout": 3000 }, "circuitBreakerOptions": { "off": true }, "retries": 0 }, "jwtServiceAddress": "symmetric" }
userUuid
Verifies that 'userUuid' is set in TargetRequest.ctx
TargetRequest.ctx: userUuid
userUuid = "abd28djf"
N/A
deviceUuid
Verifies that deviceUuid
is set in TargetRequest.ctx
TargetRequest.ctx: deviceUuid
deviceUuid = "dsoij3f4"
N/A
Registration
Add this to registry:request-plugins
to register the AuthnPlugin
.
"plugin:authn": { "main": "io.orchis.gateway.plugin.impl.authn.AuthnPlugin" }
Put the configuration into plugin:authn
object.
Configuration
For AuthnPlugin
you need to configure all authentication and entity providers.
{ (...) "plugin:authn": { "sso": see_SSO_AUTHN_PROVIDER, "user": see_USER_ENTITY_PROVIDER, "device": see_DEVICE_ENTITY_PROVIDER } }
Multiple instances
If you want to have two or more Authn plugins, with different configurations, you can register additional instances.
"plugin:custom-authn": { "main": "io.orchis.gateway.plugin.impl.authn.AuthnPlugin", "prefix": "custom-authn" }
Then put the configuration into plugin:custom-authn
object.
"plugin:custom-authn": { "methodsMapping": { (...) "jwt": "customJwtAuthnProvider" }, "entitiesMapping": { (...) }, "authnMethodProvidersConfigKey": "custom-authn-method-providers", // optional, default authn-method-providers "authnEntityProvidersConfigKey": "custom-authn-entity-providers" // optional, default authn-entity-providers }
If you set the authnMethodProvidersConfigKey
and authnEntityProvidersConfigKey
then you need to provide separate
configuration where you can define custom method and entity providers
{ "registry:custom-authn-method-providers": { "customJwtAuthnProvider": { "main": "io.orchis.gateway.plugin.impl.authn.methods.JwtAuthnProvider", "verticleConfig": { "jwtServiceAddress": "other-symmetric" } } }, "registry:custom-authn-entity-providers": { } }
AuthzPlugin
AuthzPlugin
allows validating a policy before a request reach target service.
Example
[{
"default": { (...) },
"endpoints": [{
"method": "POST",
"pathPattern": "/path-with-policy",
"requestPlugins": [
{ "name": "authz", "conf": { "policy": "policy1" } }
]
}]
}]
In this example policy1
policy is checked every time a call to /path-wih-policy
is made.
Sending original request to Authz
In order to send the original request to Authz to validate it with request-validator, you need to set sendRequest
flag
to true
(its default value is false
). See example below:
{ "name": "authz", "conf": { "policy": "policy1", "sendRequest": true } }
Enriching internal JWT with target entities
AuthzPlugin
can add extra JWT claims with entities that the user wants to act upon. To use this feature
the Authz policy should contain jwt-content validator with appropriate checks.
Currently it is possible to enrich JWT with Application entities.
The configuration has following structure:
{
...
"method": "DELETE",
"pathPattern": "/application/{appId}",
"requestPlugins": [
{
"name": "authz",
"conf": {
"policy": "DELETE_APPLICATION",
"targets": [
{
"type": "application"
"id": "path.appId",
"key": "deleteApp" // optional, defaults to `type`
}
]
}
}
]
...
}
In this example Application with ID taken from path parameter appId
is loaded and put in the JWT at content.targets.deleteApp
path.
Resolving Entity ID
id
attribute defines how to get ID of the entity. There are following methods:
-
Path param, e.g. 'path.appId'
-
Query param, e.g. 'query.appId'
-
Header value, e.g. 'header.appId'
Registration
Add this to registry:request-plugins
to register the AuthzPlugin
.
"plugin:authz": { "main": "io.orchis.gateway.plugin.impl.authz.AuthzPlugin" }
Put the configuration into plugin:authz
object.
Configuration
For AuthzPlugin
you need to configure the HTTP client to AuthzService
and JWT config for AuthzService
.
AuthzPlugin
sends SSO token to Authz. The token is read by default from 'token' request header.
You can change the header name by setting incomingSessionTokenName
.
{ (...) "plugin:authz": { "http": { "serviceName": "authz", "httpClientOptions": { "timeout": 3000 }, "circuitBreakerOptions": { "off": true }, "retries": 0 }, "jwtServiceAddress": "symmetric" } }
Read more about HTTP client configuration in Service Discovery.
Access logs
AuthzPlugin
puts authz
item into access logs with following attributes:
-
policy - validated policy name
-
status - validation status (success, failure, error)
-
recovery - boolean flag, set if validation status is
failure
, true if Authz recovery has been returned
E.g.
{ ... "authz": { "policy": "ADMIN_MANAGE_AUTHZ", "status": "success", "recovery": null } }
In order to disable it set plugin:authz.accessLogResponse
to false.
SessionPlugin
Deprecated plugin. Please use AuthnPlugin instead.
OutgoingDefaultJwtPlugin
The goal of OutgoingDefaultJwtPlugin
is to build, sign and put into request headers the JWT. It takes the
list of claims from the TargetRequest
. Thus, it should be defined as request.postFlow
plugin, so it can use
claims provided by plugins that were run before. This plugin puts the claims into content
section of the JWT.
Most often this plugin is used together with AuthnPlugin, which reads the session and user details, then puts them into claims. At the end the JWT contains session and user data. See following configuration as an example.
Example
[{
"default": { (...) },
"request": {
"postFlow": {
"plugins": [ { "name": "outgoingDefaultJwt" } ]
}
},
"endpoints": [{
"method": "GET",
"pathPattern": "/path-for-authn-plugin",
"requestPlugins": [{
"name": "authn",
"conf": { "methods": ["sso"], "entities": [] }
}]
}]
}]
For this configuration whenever a call to /path-for-authn-plugin
is made, first the authn
is executed. It uses the
sso
authentication method. For valid sso token (in this example 1234
) it injects following claims into request.
authnMethod = "sso" token = "1234" userUuid = "abd28djf" session = {"uuid": "abd28djf", "name": "John Doe"}
At the end the outgoingDefaultJwt
plugin is run which adds the signed JWT to the headers.
You can configure the output header name and pattern. See Configuration section below.
Authentication: Bearer eyJ0eiJ9.eyJpc3MiOiOTczOH0.pAPuEhv-bbt0_ssKUO_w
The decoded JWT looks like this
{
"iss": "orchis-api-gateway",
"iat": "1422779638",
"exp": "1422779942",
"content": {
"authnMethod": "sso",
"token": "1234",
"userUuid": "abd28djf",
"session": {"uuid": "abd28djf", "name": "John Doe"}
}
}
Registration
Add this to registry:request-plugins
to register the OutgoingDefaultJwtPlugin.
"plugin:outgoingDefaultJwt": { "main": "io.orchis.gateway.plugin.impl.jwt.OutgoingDefaultJwtPlugin" }
Put the configuration into plugin:outgoingDefaultJwt
object.
Configuration
The only configuration options of OutgoingDefaultJwtPlugin
is address of JwtService verticle.
{
(...)
"plugin:outgoingDefaultJwt": {
"jwtServiceAddress": "symmetric",
"authHeader": {
"name": "Authorization",
"pattern": "Bearer {}"
}
}
(...)
}
You can overwrite the default JwtService verticle in the configuration of the OutgoingDefaultJwtPlugin
.
authHeader
is optional, defaults to { "name": "Authorization", "pattern": "Bearer {}" }
.
[{
"default": { (...) },
"endpoints": [{
"method": "GET",
"pathPattern": "/path-for-authn-plugin",
"requestPlugins": [{
"name": "outgoingDefaultJwt",
"conf": { "jwtServiceAddress": "other-symmetric" }
}]
}]
}]
Note
|
If you want to register custom JwtService verticle use the prefix the define the address. config.json
|
OutgoingCustomJwtPlugin
This plugin is similar to OutgoingDefaultJwtPlugin. The only difference is that it allows to define the mapping between the claims and JWT.
Example
[{
"default": { (...) },
"request": {
"postFlow": {
"plugins": [ { "name": "outgoingDefaultJwt" } ]
}
},
"endpoints": [{
"method": "GET",
"pathPattern": "/path-for-custom-jwt-plugin",
"requestPlugins": [
{
"name": "authn",
"conf": { "methods": ["sso"], "entities": [] }
},
{
"name": "outgoingCustomJwt",
"conf": {
"mapping": {
"cnt": {
"usr": "${content.session.user}",
"lvl": "${content.session.level}",
"status": "${content.session.status}"
}
},
"defaults": {
"content.session.status": "inactive"
}
}
}
],
"request": {
"postFlow": {
"disablePlugins": ["outgoingDefaultJwt"]
}
}
}]
}]
In this example, the outgoingDefaultJwt
is disabled for /path-for-custom-jwt-plugin
endpoint, although it is
defined as global postFlow
plugin. Instead the outgoingCustomJwt
plugin is used to build the JWT.
By default, the JWT would look like this:
{
"iss": "orchis-api-gateway",
"iat": "1422779638",
"exp": "1422779942",
"content": {
"authnMethod": "sso",
"token": "1234",
"userUuid": "abd28djf",
"session": {"uuid": "abd28djf", "name": "John Doe"}
}
}
But in this case the mapping from outgoingCustomJwt
is used
"cnt": {
"usr": "${content.session.uuid}",
"ses": "${content.session}",
"status": "${content.session.status}"
}
And the final JWT looks like this
{
"usr": "abd28djf",
"ses": {"uuid": "abd28djf", "name": "John Doe"},
"status": "inactive"
}
Registration
Add this to registry:request-plugins
to register the OutgoingCustomJwtPlugin.
"plugin:outgoingCustomJwt": { "main": "io.orchis.gateway.plugin.impl.jwt.OutgoingCustomJwtPlugin" }
Put the configuration into plugin:outgoingCustomJwt
object.
Configuration
Similar to OutgoingDefaultJwtPlugin
, in conf.json
you can configure address of JwtService verticle
{
(...)
"plugin:outgoingCustomJwt": {
"jwtServiceAddress": "symmetric"
}
(...)
}
In the configuration of particular endpoint you can define the mapping and overwrite the default JwtService verticle.
{
"name": "outgoingCustomJwt",
"conf": {
"mapping": {
"cnt": {
"usr": "${content.session.user}",
"lvl": "${content.session.level}"
}
},
"jwtServiceAddress": "other-symmetric"
}
}
In the mapping you can use ${path.to.the.value}
format to reference any json object, array and value from
the default JWT. If it cannot find a reference, it tries to use a value from defaults. If it fails to find a default
value it skips this field.
Correlation ID plugin
CorrelationIdPlugin
handles correlation IDs. It can append IDs from headers for access logging, generate IDs
and rewrite correlation-id headers. All the correlation IDs are automatically added to the tracing context as baggage items.
Example
{
...
"requestPlugins": [
{
"name": "correlationId",
"conf": {
"name": "CORRELATION_ID",
"generate": "fill",
"rewrite": "X-CID"
}
}
]
...
}
-
name
of the correlation-id header -
generate
optional, either 'fill' or 'overwrite' -
rewrite
optional
Plugin can handle multiple correlation-ids. Put them at ids
attribute, e.g.:
{
...
"requestPlugins": [
{
"name": "correlationId",
"conf": {
"ids": [
{
"name": "CORRELATION_ID",
"generate": "fill",
"rewrite": "X-CID"
},
{
"name": "X-TXN_ID",
}
]
}
}
]
...
}
All correlation IDs are logged in API Gateway access log.
Examples:
-
do nothing (only use correlation-id in access logs)
{ "name": "CORRELATION_ID" }
-
generate correlation id only if missing in the header
{ "name": "CORRELATION_ID", "generate": "fill" }
-
generate correlation id even if present in the header
{ "name": "CORRELATION_ID", "generate": "overwrite" }
-
rewrite correlation id header without generating
{ "name": "CORRELATION_ID", "rewrite": "X-CID" }
BruteForcePlugin
This plugin secures an endpoint from being called too often, e.g. when someone tries to guess a password.
Example
{
...
"requestPlugins": [
{
"name": "bruteForce",
"conf": {
"maxAttempts": 3,
"blockSpan": 5,
"blockFor": 10,
"successCodes": [200, 201],
"errorCodes": [400, 401],
"identifier": {
"location": "body",
"name": "identifier"
},
"lockedResponse": {
"code": "Authentication.Locked",
"message": "The maximum number of login attempts has been reached.",
},
"counterName": "identifierpassword"
}
}
]
...
}
maxAttempts
maximum number of failed API calls before an entity is locked out
blockSpan
length of time (sec) a failed attempt will be remembered
blockFor
length of time (sec) the entity will be locked out
successCodes
clear failed attempts
errorCodes
count as failure
identifier
key to track brute force with target entity, location
is "header" or "body"
counterName
failure counter
In the example above the max attempts is 3, so a 4th unsuccessful attempt within the blockSpan specified (here within 5 seconds) will block the user for the specifed time, in this example 10 seconds, but typically set to 900 (15 min). If before being blocked a successful attempt is made, as defined by the successCodes (here 200 or 201) then failed attempts count will be reset to 0. The API has to be able to associate the failed attempts with a given identity which is defined in the 'identifier' block, here telling it to associate it with the uid in the request body.
Another example
{
"maxAttempts": 3,
"blockSpan": 5,
"blockFor": 10,
"successCodes": [],
"errorCodes": [202],
"identifier": {
"location": "body",
"name": "identifier"
},
"lockedResponse": {
"code": "Authentication.Locked",
"message": "The maximum number of login attempts has been reached.",
},
"counterName": "selfRequestResetPassword"
}
selfRequestResetPassword
is an example of an anonymous API with brute force configuration. Because anyone can click
the Forgot Password link without being authenticated, this API can be called without a valid session and we need to block the number of attempts.
In this case you can see that there are no success codes. Because we want to obfuscate the response codes for security purposes in case someone is trying to hack it, code 202, which would normally be a success code, is defined as the error code. Because this API always returns a 202 no matter what, this means that more than 3 attempts to access this API will block the user regardless. In production, blockSpan and blockFor would typically be set to 86400, or 24 hours. However, because we are still associating each attempt with a particular uid, an attack from a bot, for example, that used a different user name with each attempt would not trigger any blocking.
Even though a user is Brute Force locked by selfRequestResetPassword or another protected API, that will not prevent them from successfully logging in with their correct uid and password, because they are only Brute Force locked, not deactivated. This lock can be removed through the Admin UI.
BruteForcePlugin dependencies
BruteForcePlugin
requires an instance of io.orchis.tools.vertx.hazelcast.HazelcastService
to be deployed.
"registry:system": {
...
"cache": { "main": "io.orchis.tools.vertx.hazelcast.HazelcastVerticle" }
}
You can configure Hazelcast client in two ways:
-
provide Hazelcast client XML configuration with
-Dhazelcast.client.config
JVM param in command line -
provide Hazelcast client XML via vertx-config as a string, e.g.:
"registry:system": {
...
"cache": {
"main": "io.orchis.tools.vertx.hazelcast.HazelcastVerticle",
"verticleConfig": {
"hazelcastClientConfig": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<hazelcast-client ..."
}
}
}
The latter approach makes handling configuration more flexible, you can serve it from file system, Consul, etc.
You can override Hazelcast group configuration defined in XML (via JVM param or vertx-config) by providing group
attribute:
"registry:system": {
...
"cache": {
"main": "io.orchis.tools.vertx.hazelcast.HazelcastVerticle",
"verticleConfig": {
"group": {
"name": "dev",
"password": "dev"
}
}
}
}
There is no need to configure Hazelcast collections on the server side. BruteForcePlugin
sets values with eviction TTL.
Important
|
You need to run Api Gateway with extra JAR on classpath that provides standard or enterprise implementation of Hazelcast client. For standard implementation use Maven artifact
Don’t forget to put the JAR you require in |
Note
|
For testing purpose you can use in-memory version of HazelcastService: In-memory cache implementation
It does not support entry or lock eviction. |
Cookies Response plugin
CookieResponsePlugin
takes responsibility for rewriting response body attributes to the header Set-Cookie
values.
In general, it rewrites or remove configured response body attribute and create cookie with configured for that cookie values.
Plugin configuration:
{
(...)
"plugin:responseCookie": {
"secureCookiesEnabledHeaderName": "$ref:cookies.secureCookiesEnabledHeaderName",
"config": {
"token": {
"cookieName": "cookie1",
"attributeName": "bodyAttribute1",
"cookieSettings": "$ref:cookies.token"
},
"deviceToken": {
"cookieName": "cookie2",
"attributeName": "bodyAttribute1",
"cookieSettings": "$ref:cookies.device"
}
}
}
(...)
}
secureCookiesEnabledHeaderName
indicates on request header attribute which should have true
value to apply configured cookie actions.
cookieName
name of cookie
attributeName
name of body attribute which value will be put to cookie
Example of endpoint configuration with CookieResponsePlugin:
{
(...)
"endpoints": [
{
"endpointName": "SLA Identifier and Password Authentication",
"method": "POST",
"pathPattern": "/authn/identifierpassword",
"rewritePath": "/overlay/api/authn/identifierpassword",
"responsePlugins": [
{
"name": "responseCookie",
"conf": {
"cookies": [
{
"name": "cookie1",
"action": "rewrite"
},
{
"name": "cookie2",
"action": "remove"
}
]
}
}
]
}
]
(...)
}
cookies
list of cookies to create
name
name of cookie
action
currently supported 'rewrite' and 'remove'
Based on the examples above:
CookieResponsePlugin will execute configured cookies actions if request has 'secure_cookies_enabled' header set on true
.
If not, cookie actions will not be applied.
For 'cookie1':
If response body contains bodyAttribute1
attribute, value of it will be assigned to newly created cookie1
cookie with properties configured for that cookie.
If response body has no cookie1
attribute, cookie set action will be skipped.
For 'cookie2':
Cookie with cookie2
name will be created.
Cookie value
will be empty and maxAge=0
Devices response plugin
DevicesPlugin
is dedicated for device operations plugin.
For now is able to get configured cookie and inject it into body or JWT claims
Plugin configuration:
{
(...)
"plugin:devices": {
"secureCookiesEnabledHeaderName": "$ref:cookies.secureCookiesEnabledHeaderName",
}
(...)
}
secureCookiesEnabledHeaderName
indicates on request header attribute which should have true
value to apply configured cookie actions.
Example of endpoint configuration with applied 'devices' plugin:
{
(...)
"endpoints": [
{
"endpointName": "SLA Identifier and Password Authentication",
"method": "POST",
"pathPattern": "/authn/identifierpassword",
"rewritePath": "/overlay/api/authn/identifierpassword",
"requestPlugins": [
{
"name": "devices",
"conf": {
"rewriteCookies": [{
"name": "deviceToken",
"action": "rewrite",
"injectPlace": "body"
}]
}
}
]
}
]
(...)
}
name
name of cookie
action
currently supported 'rewrite'
injectPlace
indicates where cookie should be rewritten, currently supported 'body' and 'jwt'
Based on the examples above:
DevicesPlugin will execute configured cookies actions if request has 'secure_cookies_enabled' header set on true
.
If not, cookie actions will not be applied.
For 'rewrite' action, plugin will try to find 'deviceToken' cookie and put it’s value in request body. Will do nothing if 'deviceToken' cookie not found.
CorsPlugin
This plugin adds support for Cross-Origin Resource Sharing (CORS).
Configuration
Add the following snippet to the configuration to register and configure CorsPlugin.
{
(...)
"registry:request-plugins": {
"plugin:cors": { "main": "io.orchis.gateway.plugin.impl.cors.CorsPlugin" }
},
"plugin:cors": {
"allowCredentials": true,
"allowedHttpHeaders": ["*"],
"allowedHttpMethods": ["*"],
"allowedOrigins": ["*"],
"preflightMaxAgeInSeconds": 84000
}
}
Please refer to https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS for details regarding Access-Control headers.
Usage
To enable CORS add CorsPlugin as a preFlow plugin.
{
"rules": [
{
"default": { (...) },
"request": {
"preFlow": {
"plugins": [
{ "name": "cors" }
]
}
},
"endpoints": [ (...) ]
}
]
}
You can overwrite the default settings per endpoint
{
"rules": [
{
"default": { (...) },
"request": {
"preFlow": {
"plugins": [
{ "name": "cors" }
]
}
},
"endpoints": [
{
"method": "GET",
"pathPattern": "/cors-default-settings"
},
{
"method": "GET",
"pathPattern": "/cors-overwritten-settings",
"requestPlugins": [
{
"name": "cors",
"conf": {
"allowedHttpHeaders": ["Authentication"],
"allowedHttpMethods": ["GET", "POST"],
"allowedOrigins": ["example.com"]
}
}
],
"request": {
"preFlow": {
"disablePlugins": ["cors"]
}
}
}
]
}
]
}
AuthEvent plugin
Plugin is designed as response plugin to send events to 'authn-event-service'.
Events are related to token of particular authentication method and describe authentication level.
Configuration description
"responsePlugins": [
{
"conf": {
"event": {
"id": "TotpAuthentication",
"type": "MFA"
},
"failure": [
401
],
"success": [
200,
201
]
},
"name": "authEvent"
}
]
Will send event 'TotpAuthentication' of 'MFA' type.
In case of target service response code is in [200, 201], event goes with 'success' flag, or 'fail' when response code is [401]
Session token is taken from authnCtx(AuthnPlugin put those values there),
IdentifiersPlugin
Deprecated plugin. Please use AuthnPlugin instead.
JWT services
API Gateway uses JWT services to handle JWT signing and parsing. Plugins use JWT service to delegate JWT handling.
Symmetric JWT service
This service signs JWT with the same secret that should be used by recipient for parsing.
Symmetric JWT configuration
Attribute | Type | Description |
---|---|---|
prefix |
string |
identifies service instance, |
secret |
string |
secret used for signing and parsing JWT |
issuer |
string |
|
algorithm |
string |
signing algorithm, |
expiresIn |
string |
used to set |
toleranceInSeconds |
int |
skew time for parsing and |
prefix
attribute is used to reference the service instance by other components.
{ "registry:system": { "my-symmetric-jwt-service": { "main": "io.orchis.gateway.jwt.impl.SymmetricJwtService", "prefix": "my-symmetric", "verticleConfig": { "secret": "xyz", "issuer": "api-gateway", "algorithm": "HS256", "expiresIn": "PT60S", "toleranceInSeconds": 5 } } } }
Asymmetric JWT service
This service signs JWT with key read from privateKey
or keyStore
configuration attribute.
Key used for parsing is retrieved from io.orchis.gateway.jwt.PublicKeyProvider
by kid
JWT header value.
Asymmetric JWT configuration
Attribute | Type | Description |
---|---|---|
prefix |
string |
identifies service instance, |
serialNumber |
string |
|
serviceId |
string |
|
instanceId |
string |
|
spiffeId |
string |
SPIFFE identifier |
algorithm |
string |
signing algorithm, |
expiresIn |
string |
used to set |
privateKey |
string |
Base64-encoded PKCS1 key |
keyStore |
json object |
java.security.Keystore configuration, has |
toleranceInSeconds |
int |
skew time for parsing and |
prefix
attribute is used to reference the service instance by other components.
{ "registry:system": { "my-asymmetric-jwt-service": { "main": "io.orchis.gateway.jwt.impl.AsymmetricJwtService", "prefix": "my-asymmetric", "verticleConfig": { "keystore": { "keyPassword": "password", "keyAlias": "app", "path": "/keys/keystore.p12" }, "expiresIn": "PT60S", "serialNumber": "api-gateway", "spiffeId": "spiffe://A/ns/B/sa/C/ver/D/ins/E" } } } }
Service Discovery
API Gateway enables you to utilize external service registration and discovery services. Out of the box it provides integration with consul or utilization of fixed service provider( static file based). Below configuration file represents the example configuration of such integration.
{
"consul-sd-provider": {
"consul": {
"host": "localhost",
"port": 8500,
"ssl": false
},
"discover": {
"scan-period": 3000
}
},
"registry:sd": {
"sd": { "main": "io.orchis.tools.vertx.sd.SdVerticle" }
},
"registry:system": {
"fixed-sd-provider": { "main": "io.orchis.tools.vertx.sd.provider.FixedSdProvider" }
},
"circuit-breakers": {
"off": true
},
"fixed-sd-provider": {
"records": [
{
"name": "authz",
"metadata": {
"ID": "authz:localhost:7777"
},
"location": {
"host": "localhost",
"port": 7777,
"ssl": false
}
}
]
}
}
Appendix A: Sample config
{
"scanPeriod": 5000,
"stores": [
{
"type": "file",
"format": "json",
"config": {
"path": "src/main/resources/config.json"
}
},
{
"type": "file",
"format": "json",
"config": {
"path": "src/main/resources/rules.json"
}
},
{
"type": "file",
"format": "json",
"config": {
"path": "src/main/resources/secrets.json"
}
}
]
}
{
"apiServer": {
"http": {
"port": 7772
},
"routes": [
{
"id": "access-log-route",
"urlPath": "/*"
},
{
"id": "docs-route",
"method": "GET",
"urlPath": "/docs/*"
},
{
"id": "jwt-filter",
"urlPath": "/*"
},
{
"id": "getBruteForceConfig",
"method": "GET",
"urlPath": "/bruteforce/config"
},
{
"id": "getUserBruteForceAttempts",
"method": "GET",
"urlPath": "/bruteforce/:counterName/identifier/:identifier"
},
{
"id": "deleteUserBruteForceAttempts",
"method": "DELETE",
"urlPath": "/bruteforce/:counterName/identifier/:identifier"
},
{
"id": "deleteBruteForceAttempts",
"method": "DELETE",
"urlPath": "/bruteforce/:counterName/identifiers"
}
]
},
"openApi": {
"publicLocation": {
"host": "localhost",
"port": 80,
"ssl": false
},
"defaultSource": {
"path": "/docs/index-resolved.yaml"
},
"defaultConverter": {
"defaults": {
"host" : "$ref:openApi.publicLocation.host:string:localhost",
"ssl" : "$ref:openApi.publicLocation.ssl:boolean:false"
},
"processors": {
"pre": ["dropSecurityDefinitions"]
}
},
"excludedServices": [
"virtual"
]
},
"registry:openApiPreProcessors": {
"dropSecurityDefinitions": {
"main": "io.orchis.gateway.plugin.impl.authn.openapi.DropSecurityDefinitionsPreProcessor",
"verticleConfig": {}
}
},
"registry:routes": {
"access-log-route": { "main": "io.orchis.tools.vertx.server.api.routes.impl.AccessLogRoute" },
"docs-route": { "main": "io.orchis.tools.vertx.server.api.routes.impl.DocsRoute" },
"jwt-filter": { "main": "io.orchis.tools.vertx.server.api.routes.impl.JwtFilter"},
"getBruteForceConfig": { "main": "io.orchis.gateway.admin.route.bruteforce.GetBruteForceConfigRoute" },
"getUserBruteForceAttempts": { "main": "io.orchis.gateway.admin.route.bruteforce.GetUserBruteForceAttemptsRoute" },
"deleteUserBruteForceAttempts": { "main": "io.orchis.gateway.admin.route.bruteforce.DeleteUserBruteForceAttemptsRoute" },
"deleteBruteForceAttempts": { "main": "io.orchis.gateway.admin.route.bruteforce.DeleteBruteForceAttemptsRoute" }
},
"vertx": {
"logger-delegate-factory-class-name": "io.vertx.core.logging.SLF4JLogDelegateFactory"
},
"orchis": {
"app": {
"port": 7773,
"rules": "$ref:rules",
"defaultProxyRules": "$ref:defaultProxyRules",
"defaultProxyRulesEnabled": false,
"server": {
"http": {
"ssl": false
}
},
"docsEnabled": true
}
},
"plugin:outgoingDefaultJwt": {
"jwtServiceAddress": "symmetric"
},
"plugin:outgoingCustomJwt": {
"jwtServiceAddress": "symmetric"
},
"plugin:authz": {
"http": {
"serviceName": "authz",
"httpClientOptions": {
"timeout": 3000
},
"circuitBreakerOptions": { "off": true },
"retries": 0
},
"jwtServiceAddress": "symmetric",
"entityProviders": {
"application": "applicationByUuidProvider",
"user": "userAuthzProvider"
}
},
"registry:authz-entity-providers": {
"applicationByUuidProvider": {
"main": "io.orchis.gateway.plugin.impl.authz.entities.ApplicationEntityByUuidProvider"
},
"userAuthzProvider": {
"main": "io.orchis.gateway.plugin.impl.authz.entities.UserEntityProvider",
"verticleConfig": "$ref:user-service"
}
},
"cookies": {
"secureCookiesEnabledHeaderName": "secure_cookies_enabled",
"token": {
"maxAge": 1800,
"path": "/",
"domain": ".local.orchis.syntegrity.com",
"secure": false,
"httpOnly": true
},
"device": {
"maxAge": 2147483647,
"path": "/",
"domain": "",
"secure": false,
"httpOnly": true
}
},
"plugin:authn": {
"methodsMapping": {
"anonymous": "anonymousAuthnProvider",
"authorizationCodeOAuth": "authorizationCodeOAuthProvider",
"authorizationCodeOAuthIntrospection": "authorizationCodeOAuthIntrospectionProvider",
"clientCredentialsOAuth": "clientCredentialsOAuthProvider",
"hmac": "legacyHmac",
"jwt": "jwtAuthnProvider",
"sso": "ssoAuthnProvider"
},
"entitiesMapping": {
"sso": {
"customerId": "customerIdProvider",
"device": "deviceEntityProvider",
"deviceUuid": "deviceUuidProvider",
"realm": "realmProvider",
"token": "tokenProvider",
"user": "userEntityProvider",
"userUuid": "userUuidProvider"
},
"authorizationCodeOAuth": {
"customerId": "customerIdFromUserEntityProvider",
"token": "tokenProvider",
"user": "userEntityProvider",
"userUuid": "userUuidProvider"
},
"authorizationCodeOAuthIntrospection": {
"customerId": "customerIdFromUserEntityProvider",
"token": "tokenProvider",
"user": "userEntityProvider",
"userUuid": "userUuidProvider"
},
"clientCredentialsOAuth": {
"applicationByClientId": "applicationByClientIdProvider"
},
"hmac": {
"customerId": "customerIdProvider",
"realm": "realmProvider",
"userStatus": "userStatusProvider",
"userUuid": "userUuidProvider"
}
},
"openApi": {
"oauthUrls": {
"authorizationUrl": {
"host": "$ref:openApi.publicLocation.host:string:localhost",
"path": "/oauth/authorize",
"port" : "$ref:openApi.publicLocation.port:int:80",
"ssl" : "$ref:openApi.publicLocation.ssl:boolean:false"
},
"tokenUrl": {
"host": "$ref:openApi.publicLocation.host:string:localhost",
"path": "/oauth/token",
"port" : "$ref:openApi.publicLocation.port:int:80",
"ssl" : "$ref:openApi.publicLocation.ssl:boolean:false"
}
},
"securityDefinitionsMapping": {
"authorizationCodeOAuth": {
"type": "oauth2",
"flows": ["implicit", "authorizationCode", "password"]
},
"authorizationCodeOAuthIntrospection": {
"type": "oauth2",
"flows": ["implicit", "authorizationCode", "password"]
},
"clientCredentialsOAuth": {
"type": "oauth2",
"flows": ["clientCredentials"]
},
"sso": {
"type": "apiKey",
"definitionName": "SsoToken",
"in": "header",
"name": "token"
}
}
}
},
"listOpenApi": {
"location": {
"host" : "$ref:openApi.publicLocation.host:string:localhost",
"ssl" : "$ref:openApi.publicLocation.ssl:boolean:false",
"port" : "$ref:openApi.publicLocation.port:int:80",
"basePath": "$ref:app.openApi.basePath:string:/api/openapi"
}
},
"registry:authn-method-providers": {
"anonymousAuthnProvider": {
"main": "io.orchis.gateway.plugin.impl.authn.methods.AnonymousAuthnProvider"
},
"ssoAuthnProvider": {
"main": "io.orchis.gateway.plugin.impl.authn.methods.SsoAuthnProvider",
"verticleConfig": {
"sessionService": {
"ssl": false,
"host": "local.orchis.syntegrity.com",
"path": "/overlay/api",
"port": 7080,
"timeout": 3000,
"debug": false
},
"csrfTokenHeader": "X-CSRF-TOKEN",
"tokenHeaderName": "token",
"tokenCookieName": "token",
"tokenCookieSettings": "$ref:cookies.token",
"userIdName": "uuid",
"jwtServiceAddress": "symmetric"
}
},
"jwtAuthnProvider": {
"main": "io.orchis.gateway.plugin.impl.authn.methods.JwtAuthnProvider",
"verticleConfig": {
"jwtServiceAddress": "symmetric"
}
},
"authorizationCodeOAuthProvider": { "main": "io.orchis.gateway.plugin.impl.authn.methods.OAuthAuthorizationCodeAuthnProvider" },
"clientCredentialsOAuthProvider": { "main": "io.orchis.gateway.plugin.impl.authn.methods.OAuthClientCredentialsAuthnProvider" },
"legacyHmac": {
"main": "io.orchis.gateway.plugin.impl.authn.methods.LegacyHmacProvider",
"verticleConfig": {
"authorization": "x-orchis-authorization",
"request": "x-orchis-request",
"date": "x-orchis-date",
"key": "apiKey",
"authHeaderPrefix": "orchis-hmac",
"dateFormat": "EEE, d MMM yyyy HH:mm:ss Z",
"limitInMinutes": 15,
"realm": "internal-applications"
}
},
"authorizationCodeOAuthIntrospectionProvider": {
"main": "io.orchis.gateway.plugin.impl.authn.methods.OAuthAuthorizationCodeIntrospectionAuthnProvider",
"verticleConfig": {
"http": {
"serviceLocation": {
"host": "localhost",
"port": 9999,
"ssl": false,
"root": "/oauth"
}
},
"introspectPath": "/api/introspect"
}
}
},
"registry:authn-entity-providers": {
"userUuidProvider": { "main": "io.orchis.gateway.plugin.impl.authn.entities.UserUuidProvider" },
"userStatusProvider": {
"main": "io.orchis.gateway.plugin.impl.authn.entities.UserStatusProvider"
},
"sessionProvider": { "main": "io.orchis.gateway.plugin.impl.authn.entities.SSOSessionProvider" },
"sessionIdProvider": { "main": "io.orchis.gateway.plugin.impl.authn.entities.SessionIdProvider" },
"jwtContentProvider": { "main": "io.orchis.gateway.plugin.impl.authn.entities.JWTContentProvider" },
"realmProvider": { "main": "io.orchis.gateway.plugin.impl.authn.entities.RealmProvider" },
"userEntityProvider": {
"main": "io.orchis.gateway.plugin.impl.authn.entities.UserEntityProvider",
"verticleConfig": "$ref:user-service"
},
"deviceUuidProvider": { "main": "io.orchis.gateway.plugin.impl.authn.entities.DeviceUuidProvider" },
"customerIdProvider": {
"main": "io.orchis.gateway.plugin.impl.authn.entities.CustomerIdProvider"
},
"customerIdFromUserEntityProvider": {
"main": "io.orchis.gateway.plugin.impl.authn.entities.CustomerIdFromUserEntityProvider",
"verticleConfig": "$ref:user-service"
},
"tokenProvider": {
"main": "io.orchis.gateway.plugin.impl.authn.entities.SessionIdProvider"
},
"deviceEntityProvider": {
"main": "io.orchis.gateway.plugin.impl.authn.entities.DeviceEntityProvider",
"verticleConfig": {
"deviceService": {
"serviceName": "deviceService",
"httpClientOptions": {
"timeout": 3000
},
"circuitBreakerOptions": { "off": true },
"retries": 0
},
"jwtServiceAddress": "symmetric"
}
},
"applicationByClientIdProvider": {
"main": "io.orchis.gateway.plugin.impl.authn.entities.ApplicationByOAuthClientIdProvider",
"verticleConfig": {
"applicationService": {
"serviceName": "applicationService",
"httpClientOptions": {
"timeout": 3000
},
"circuitBreakerOptions": { "off": true },
"retries": 0
},
"jwtServiceAddress": "symmetric"
}
}
},
"plugin:session": {
"sessionService": {
"ssl": false,
"host": "local.orchis.syntegrity.com",
"path": "/overlay/api",
"port": 7080,
"timeout": 3000,
"debug": false
},
"sessionTokenName": "token",
"userIdName": "uuid",
"jwtServiceAddress": "symmetric"
},
"plugin:identifiers": {
"sessionService": {
"ssl": false,
"host": "local.orchis.syntegrity.com",
"path": "/overlay/api",
"port": 7080,
"timeout": 3000,
"debug": false
},
"jwtServiceAddress": "symmetric",
"authnMethod":{
"session": "token"
}
},
"consul-sd-provider": {
"consul": {
"host": "localhost",
"port": 8500,
"ssl": false
},
"discover": {
"scan-period": 3000
}
},
"plugin:cors": {
"allowCredentials": true,
"allowedHttpHeaders": ["*"],
"allowedHttpMethods": ["*"],
"allowedOrigins": ["*"],
"preflightMaxAgeInSeconds": 600
},
"plugin:responseCookie": {
"secureCookiesEnabledHeaderName": "$ref:cookies.secureCookiesEnabledHeaderName",
"config": {
"token": {
"cookieName": "token",
"attributeName": "token",
"cookieSettings": "$ref:cookies.token"
},
"deviceToken": {
"cookieName": "deviceToken",
"attributeName": "deviceToken",
"cookieSettings": "$ref:cookies.device"
}
}
},
"plugin:devices": {
"secureCookiesEnabledHeaderName": "$ref:cookies.secureCookiesEnabledHeaderName"
},
"plugin:authEvent": {
"enabled": true
},
"registry:request-plugins": {
"plugin:bruteForce": { "main": "io.orchis.gateway.plugin.impl.bruteforce.BruteForcePlugin" },
"plugin:cors": { "main": "io.orchis.gateway.plugin.impl.cors.CorsPlugin" },
"plugin:correlationId": { "main": "io.orchis.gateway.plugin.impl.correlation.CorrelationIdPlugin" },
"plugin:headerToCtx": { "main": "io.orchis.gateway.plugin.impl.headers.HeaderToFlowCtxPlugin" },
"plugin:authn": { "main": "io.orchis.gateway.plugin.impl.authn.AuthnPlugin" },
"plugin:authn-proxy": { "main": "io.orchis.gateway.plugin.impl.authn.AuthnProxyPlugin" },
"plugin:authz": { "main": "io.orchis.gateway.plugin.impl.authz.AuthzPlugin" },
"plugin:session": { "main": "io.orchis.gateway.plugin.impl.session.SessionPlugin" },
"plugin:outgoingDefaultJwt": { "main": "io.orchis.gateway.plugin.impl.jwt.OutgoingDefaultJwtPlugin" },
"plugin:outgoingCustomJwt": { "main": "io.orchis.gateway.plugin.impl.jwt.OutgoingCustomJwtPlugin" },
"plugin:identifiers": { "main": "io.orchis.gateway.plugin.impl.identifiers.IdentifiersPlugin" },
"plugin:devices": { "main": "io.orchis.gateway.plugin.impl.devices.DevicesPlugin" },
"plugin:respond": { "main": "io.orchis.gateway.plugin.impl.respond.RespondPlugin" }
},
"registry:response-plugins": {
"plugin:responseCookie": { "main": "io.orchis.gateway.plugin.impl.cookies.CookieResponsePlugin" },
"plugin:authEvent": { "main": "io.orchis.gateway.plugin.impl.authevent.AuthEventsPlugin" }
},
"registry:sd": {
"sd": { "main": "io.orchis.tools.vertx.sd.SdVerticle" }
},
"registry:system": {
"fixed-sd-provider": { "main": "io.orchis.tools.vertx.sd.provider.FixedSdProvider" },
"symmetric-jwt-service": { "main": "io.orchis.gateway.jwt.impl.SymmetricJwtService" },
"jwt-service": { "main": "io.orchis.tools.vertx.jwt.impl.JwtServiceVerticle" },
"cache": { "main": "io.orchis.tools.vertx.hazelcast.inmemory.InMemoryHazelcastVerticle" },
"log-access-log-persister": { "main": "io.orchis.gateway.accesslog.LogAccessLogPersister"},
"kafka-access-log-persister": {
"main": "io.orchis.gateway.accesslog.KafkaAccessLogPersister",
"disabled": true,
"verticleConfig": {
"topic": "api-gateway-access-log",
"kafkaConfig": "$ref:kafka"
}
}
},
"registry:open-api": {
"openapi-service": { "main": "io.orchis.gateway.openapi.OpenApiServiceVerticle" },
"openapi-converter": { "main": "io.orchis.gateway.openapi.OpenApiConverterVerticle" }
},
"registry:service-clients": {
"jwt-authenticator": { "main": "com.cloudentity.services.openapi.tools.httpclient.vertxscala.auth.JwtAuthImpl" },
"application-service-client": {
"main": "com.cloudentity.services.applicationservice.client.api.ApplicationApiClientImpl",
"verticleConfig": {
"serviceLocation": "$ref:application-service-location"
}
},
"authn-events-service-client": {
"main": "com.cloudentity.services.authneventsservice.client.api.AutheventApiClientImpl",
"verticleConfig": {
"serviceLocation": "$ref:authn-events-service-location"
}
}
},
"user-service": {
"userService": {
"serviceName": "userService",
"httpClientOptions": {
"timeout": 3000
},
"circuitBreakerOptions": { "off": true },
"retries": 0
},
"jwtServiceAddress": "symmetric"
},
"jwt-service": {
"secret": "$ref:secrets.jwt",
"issuer": "orchis-api-gateway"
},
"symmetric-jwt-service": {
"secret": "$ref:secrets.jwt",
"issuer": "orchis-api-gateway",
"expiresIn": "PT60S"
},
"circuit-breakers": {
"off": true
},
"tracing": {
"JAEGER_SERVICE_NAME": "orchis-api-gateway"
},
"fixed-sd-provider": {
"records": [
{
"name": "authz",
"location": {
"host": "localhost",
"port": 7777,
"ssl": false
}
},
{
"name": "s5d",
"location": {
"host": "localhost",
"port": 3001,
"ssl": false
}
},
{
"name": "userService",
"location": {
"host": "127.0.0.1",
"port": 7080,
"ssl": false
}
},
{
"name": "deviceService",
"location": {
"host": "127.0.0.1",
"port": 7080,
"ssl": false
}
},
{
"name": "applicationService",
"location": "$ref:application-service-location"
},
{
"name": "bruteforce-admin",
"location": {
"host": "127.0.0.1",
"port": 7772,
"ssl": false
}
}
]
},
"application-service-location": {
"host": "127.0.0.1",
"port": 7080,
"ssl": false
},
"authz-service-location": {
"host": "127.0.0.1",
"port": 9050,
"ssl": false
},
"authn-events-service-location": {
"host": "127.0.0.1",
"port": 7654,
"ssl": false
},
"kafka": {
"producerConfig": {
"bootstrap.servers": "localhost:9092"
}
}
}
Appendix B: Sample rules
{
"rules": [
{
"default": {
"targetHost": "virtual",
"targetPort": 0,
"pathPrefix": "",
"dropPrefix": false
},
"endpoints": [
{
"method": "POST",
"pathPattern": "/authn-plugin",
"requestPlugins": [
{
"name": "authn-proxy",
"conf": {}
}
]
}
]
},
{
"default": {
"targetHost": "localhost",
"targetPort": 8090,
"pathPrefix": "",
"dropPrefix": false,
"requestPlugins": [
{
"name": "identifiers",
"conf": {
"authnMethods": [
"session"
],
"requiredIds": {
"userUuid": "uuid",
"deviceUuid": "deviceUuid"
}
}
}
]
},
"request": {
"postFlow": {
"plugins": [
{
"name": "outgoingDefaultJwt",
"conf": {}
}
]
}
},
"endpoints": [
{
"method": "POST",
"pathPattern": "/device/trust",
"rewritePath": "/relations/trust/trusted",
"requestPlugins": [
{
"name": "identifiers",
"conf": {
"authnMethods": [
"session"
],
"requiredIds": {
"userUuid": "uuid",
"deviceUuid": "deviceUuid"
}
}
},
{
"name": "session",
"conf": {}
},
{
"name": "authz",
"conf": {
"policy": "TRUST_CURRENT_DEVICE"
}
}
]
},
{
"method": "DELETE",
"pathPattern": "/device/trust",
"rewritePath": "/relations/trust",
"requestPlugins": [
{
"name": "identifiers",
"conf": {
"authnMethods": [
"session"
],
"requiredIds": {
"userUuid": "uuid",
"deviceUuid": "deviceUuid"
}
}
},
{
"name": "session",
"conf": {}
},
{
"name": "authz",
"conf": {
"policy": "UNTRUST_CURRENT_DEVICE"
}
}
]
},
{
"method": "DELETE",
"pathPattern": "/devices/{deviceUuid}/trust",
"rewritePath": "/relations/device/{deviceUuid}/trust",
"requestPlugins": [
{
"name": "identifiers",
"conf": {
"authnMethods": [
"session"
],
"requiredIds": {
"userUuid": "uuid",
"deviceUuid": "deviceUuid"
}
}
},
{
"name": "session",
"conf": {}
},
{
"name": "authz",
"conf": {
"policy": "UTRUST_DEVICE"
}
}
]
},
{
"method": "DELETE",
"pathPattern": "/user/{userUuid}/devices/trust",
"rewritePath": "/relations/user/{userUuid}/device/trust",
"requestPlugins": [
{
"name": "session",
"conf": {}
},
{
"name": "authz",
"conf": {
"policy": "UNTRUST_USER_DEVICES"
}
}
]
},
{
"method": "POST",
"pathPattern": "/device/distrust",
"rewritePath": "/relations/trust/distrusted",
"requestPlugins": [
{
"name": "identifiers",
"conf": {
"authnMethods": [
"session"
],
"requiredIds": {
"userUuid": "uuid",
"deviceUuid": "deviceUuid"
}
}
},
{
"name": "session",
"conf": {}
},
{
"name": "authz",
"conf": {
"policy": "DISTRUST_CURRENT_DEVICE"
}
}
]
},
{
"method": "GET",
"pathPattern": "/user/devices/{relationType}",
"rewritePath": "/devices/user/{relationType}",
"requestPlugins": [
{
"name": "identifiers",
"conf": {
"authnMethods": [
"session"
],
"requiredIds": {
"userUuid": "uuid"
}
}
},
{
"name": "authz",
"conf": {
"policy": "FULLY_AUTHENTICATED"
}
}
]
}
]
},
{
"default": {
"targetService": "authz",
"pathPrefix": "/authz",
"dropPrefix": true,
"requestPlugins": [
{
"name": "authn",
"conf": {
"methods": [
"sso"
],
"entities": [
"user"
]
}
},
{
"name": "authz",
"conf": {
"policy": "ADMIN_MANAGE_AUTHZ"
}
}
]
},
"request": {
"postFlow": {
"plugins": [
{
"name": "outgoingDefaultJwt",
"conf": {}
}
]
}
},
"endpoints": [
{
"method": "POST",
"pathPattern": "/policy"
},
{
"method": "DELETE",
"pathPattern": "/policy/{policyName}"
},
{
"method": "GET",
"pathPattern": "/policy/{policyName}"
},
{
"method": "GET",
"pathPattern": "/policies"
},
{
"method": "PUT",
"pathPattern": "/policy/{policyName}"
},
{
"method": "POST",
"pathPattern": "/policy/check"
},
{
"method": "POST",
"pathPattern": "/policy/{policyName}/validate",
"requestPlugins": [
{
"name": "authn",
"conf": {
"methods": ["sso", "authorizationCodeOAuth", "anonymous"]
}
}
]
}
]
},
{
"default": {
"targetHost": "localhost",
"targetPort": 7070,
"pathPrefix": "",
"dropPrefix": false,
"requestPlugins": [
{
"name": "session",
"conf": {}
},
{
"name": "authz",
"conf": {
"policy": "SELF_MANAGE_APPLICATIONS"
}
}
]
},
"request": {
"postFlow": {
"plugins": [
{
"name": "outgoingDefaultJwt",
"conf": {}
}
]
}
},
"endpoints": [
{
"method": "GET",
"pathPattern": "/applications"
},
{
"method": "POST",
"pathPattern": "/applications"
},
{
"method": "GET",
"pathPattern": "/application/{uuid}"
},
{
"method": "POST",
"pathPattern": "/application/{uuid}/capability/oauthClient"
},
{
"method": "GET",
"pathPattern": "/application/{uuid}/capability/oauthClient"
},
{
"method": "PUT",
"pathPattern": "/application/{uuid}/capability/oauthClient"
},
{
"method": "POST",
"pathPattern": "/application/{uuid}/capability/oauthClient/secret"
},
{
"method": "GET",
"pathPattern": "/application/{uuid}/capability/resourceServer/permissions"
},
{
"method": "POST",
"pathPattern": "/application/{uuid}/capability/resourceServer/permissions"
},
{
"method": "DELETE",
"pathPattern": "/application/{uuid}/capability/resourceServer/permission/{name}"
},
{
"method": "GET",
"pathPattern": "/application/capability/resourceServers"
},
{
"method": "POST",
"pathPattern": "/application/{uuid}/capability/gatewayprotected"
},
{
"method": "PUT",
"pathPattern": "/application/{uuid}/capability/gatewayprotected"
},
{
"method": "GET",
"pathPattern": "/application/{uuid}/capability/gatewayprotected"
},
{
"method": "POST",
"pathPattern": "/application/{uuid}/capability/edgegateway"
},
{
"method": "POST",
"pathPattern": "/application/{uuid}/capability/edgegateway/protected/{protectedUuid}"
},
{
"method": "DELETE",
"pathPattern": "/application/{uuid}/capability/edgegateway/protected/{protectedUuid}"
},
{
"method": "GET",
"pathPattern": "/application/{uuid}/capability/edgegateway/protected"
}
]
},
{
"default": {
"targetService": "bruteforce-admin",
"pathPrefix": "/ops/bruteforce",
"dropPrefix": true
},
"request": {
"preFlow": {
"plugins": [
{
"name": "authn",
"conf": {
"methods": ["sso"]
}
}
]
},
"postFlow": {
"plugins": [
{
"name": "outgoingDefaultJwt",
"conf": {}
}
]
}
},
"endpoints": [
{
"method": "GET",
"pathPattern": "/config",
"rewritePath": "/bruteforce/names",
"requestPlugins": [
{
"name": "authz",
"conf": {
"policy": "GET_BRUTE_FORCE_CONFIG_POLICY"
}
}
]
},
{
"method": "GET",
"pathPattern": "/{counterName}/user/{identifier}",
"rewritePath": "/bruteforce/{counterName}/identifier/{identifier}",
"requestPlugins": [
{
"name": "authz",
"conf": {
"policy": "GET_USER_BRUTE_FORCE_ATTEMPTS_POLICY"
}
}
]
},
{
"method": "DELETE",
"pathPattern": "/{counterName}/user/{identifier}",
"rewritePath": "/bruteforce/{counterName}/identifier/{identifier}",
"requestPlugins": [
{
"name": "authz",
"conf": {
"policy": "DELETE_USER_BRUTE_FORCE_ATTEMPTS_POLICY"
}
}
]
},
{
"method": "DELETE",
"pathPattern": "/{counterName}/users",
"rewritePath": "/bruteforce/{counterName}/identifiers",
"requestPlugins": [
{
"name": "authz",
"conf": {
"policy": "DELETE_BRUTE_FORCE_ATTEMPTS_POLICY"
}
}
]
}
]
}
]
}