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.hosttoX-Forwarded-Forheaders -
Always add
remote-address.protocoltoX-Forwarded-Protoheaders -
If
Hostheader is set add it toX-Forwarded-Hostheaders -
If True Client IP header is missing then set it to first
X-Forwarded-Forvalue. 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/[^/]+/sessionpath 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.methodequalsrequest.methodandrequest.pathmatches regular expression built by concatenatingRule.pathPrefixandRule.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/itemswith body and headers copied from the original request. Note the/my_serviceprefix 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.methodequalsrequest.methodandrequest.pathmatches regular expression built by concatenatingRule.pathPrefixandRule.pathPattern.-
The Rule #4 is found.
-
Call target service.
-
Calling POST
http://my.service.com:8080/service/1234with body and headers copied from the original request. Note the/my_serviceprefix 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"
}
}
]
...
}
-
nameof the correlation-id header -
generateoptional, either 'fill' or 'overwrite' -
rewriteoptional
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.configJVM 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"
}
}
]
}
]
}
]
}