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:

config.json
{
  "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 to X-Forwarded-For headers

  • Always add remote-address.protocol to X-Forwarded-Proto headers

  • If Host header is set add it to X-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 at orchis.app.proxyHeaders.inputTrueClientIp, default X-Real-IP

  • Outgoing True Client IP header name is read from configuration at orchis.app.proxyHeaders.outputTrueClientIp, default X-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:

logging all request headers
{
  "request": {
    "headers": {
      "all": true
    }
}

If you want to select specific headers then set whitelist attribute:

logging selected request headers
{
  "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 as map
{
  ...
  "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

service-a.json or service-b.json
[
  {
    "default": ...
    "endpoints": ...
  }
]

Logs

Access logs

Access logs have following JSON format:

Access logs 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 pathPrefix to match API request path

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.

flow

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:

plugins example
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

/static/.*

N/A

/my_service

my.service.com

8080

[]

2

GET

/items

N/A

/my_service

my.service.com

8080

[]

3

GET

/items/items_id

N/A

/my_service

my.service.com

8080

[]

4

GET

/items/to/rewrite{item_id}

/service/{item_id}

/my_service

my.service.com

8080

[]

5

POST

/items

N/A

/my_service

my.service.com

8080

[ { "name": "authz", "conf": { "policy": "CREATE_ITEM" } } ]

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'
  1. Find a Rule where Rule.method equals request.method and request.path matches regular expression built by concatenating Rule.pathPrefix and Rule.pathPattern.

    1. The Rule #5 is found.

  2. Apply request plugins.

    1. The authz plugin is applied, checking that the requestor is authorized to perform the call.

  3. Call target service.

    1. 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.

  4. Apply response plugins.

    1. No response plugins is applied.

  5. Return the final response.

Request not matching any Rules

request.method = 'PUT'
request.path = '/my_service/items/1234'
  1. Find a Rule.

    1. 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'
  1. Find a Rule where Rule.method equals request.method and request.path matches regular expression built by concatenating Rule.pathPrefix and Rule.pathPattern.

    1. The Rule #4 is found.

    2. Call target service.

    3. 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.

  2. Apply response plugins.

    1. No response plugins is applied.

  3. 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

rules.json
[{
  "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.

input
Header: ${conf.tokenHeaderName}
Cookie: ${conf.tokenCookieName}
output
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
configuration
{
  "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.

input
Header: Authorization, pattern: 'Bearer {accessToken}'
output
authnMethod = "authorizationCodeOAuth"
userUuid = "abd28djf"
configuration
N/A
jwt
input
Header: Authorization, pattern: 'Bearer {jwt}'
output
jwt.claims
configuration
{
  "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:

  1. parse authorization header

  2. validate version

  3. validate signature method

  4. validate timestamp

  5. validate nonce

  6. validate signature

    1. load key based on oauth_consumer_key

    2. normalize request (calculate base string)

    3. verify signature (recalculate signature and compare to provided oauth_signature)

input
Header: Authorization, pattern: 'OAuth {params}'
example input header
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"
output
authnMethod = "oauth10"
configuration
{
  "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"
  }
}

publicKeys is a map where key is the name of key and value is base64encoded string of RSA public key

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:

custom consumerKeyRegex
"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.

input
TargetRequest.ctx: userUuid
output
user = {"uuid": "abd28djf", "name": "John Doe"}
configuration
{
  "userService": {
    "serviceName": "userService",
    "httpClientOptions": {
      "timeout": 3000
    },
    "circuitBreakerOptions": { "off": true },
    "retries": 0
  },
  "jwtServiceAddress": "symmetric"
}
device

Loads Cloudentity Device by deviceUuid.

input
TargetRequest.ctx: deviceUuid
output
device = {"uuid": "dsoij3f4", ... }
configuration
{
  "deviceService": {
    "serviceName": "deviceService",
    "httpClientOptions": {
      "timeout": 3000
    },
    "circuitBreakerOptions": { "off": true },
    "retries": 0
  },
  "jwtServiceAddress": "symmetric"
}
userUuid

Verifies that 'userUuid' is set in TargetRequest.ctx

input
TargetRequest.ctx: userUuid
output
userUuid = "abd28djf"
configuration
N/A
deviceUuid

Verifies that deviceUuid is set in TargetRequest.ctx

input
TargetRequest.ctx: deviceUuid
output
deviceUuid = "dsoij3f4"
configuration
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

rules.json
[{
  "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:

sending request
{ "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:

Target entities
{
  ...
  "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

rules.json
[{
  "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.

Claims
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.

Headers
Authentication: Bearer eyJ0eiJ9.eyJpc3MiOiOTczOH0.pAPuEhv-bbt0_ssKUO_w

The decoded JWT looks like this

JWT
{
  "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.

conf.json
{
  (...)
  "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 {}" }.

rules.json
[{
  "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
{
  (...)
  "registry:system": {
    (...)
    "other-symmetric-jwt-service": {
     "main": "io.orchis.gateway.jwt.impl.SymmetricJwtService",
     "prefix": "other-symmetric"
    },
  },
  "other-symmetric-jwt-service": {
    "secret": "other-secret",
    "issuer": "orchis-api-gateway",
    "expiresIn": "PT60S"
  },
}

OutgoingCustomJwtPlugin

This plugin is similar to OutgoingDefaultJwtPlugin. The only difference is that it allows to define the mapping between the claims and JWT.

Example

rules.json
[{
  "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:

Default JWT
{
  "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

Mapping
"cnt": {
  "usr": "${content.session.uuid}",
  "ses": "${content.session}",
  "status": "${content.session.status}"
}

And the final JWT looks like this

Mapped JWT
{
  "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

conf.json
{
  (...)
  "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

rules.json
{
  ...
  "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.:

correlationId configuration with multiple IDs
{
  ...
  "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

rules.json
{
  ...
  "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

bruteForce plugin config
{
  "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.

Hazelcast cache implementation
"registry:system": {
  ...
  "cache": { "main": "io.orchis.tools.vertx.hazelcast.HazelcastVerticle" }
}

You can configure Hazelcast client in two ways:

  1. provide Hazelcast client XML configuration with -Dhazelcast.client.config JVM param in command line

  2. provide Hazelcast client XML via vertx-config as a string, e.g.:

Hazelcast cache with XML config
"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:

Hazelcast cache with XML config
"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 io.orchis.tools:vertx-hazelcast-impl:jar, for enterprise io.orchis.tools:vertx-hazelcast-enterprise-impl:jar, e.g.:

java -Dhazelcast.client.config=hazelcast.xml -cp "libs/*:api-gateway.jar" io.orchis.gateway.Application run -conf meta-config.json

Don’t forget to put the JAR you require in libs folder.

Note

For testing purpose you can use in-memory version of HazelcastService:

In-memory cache implementation
"registry:system": {
  ...
  "cache": { "main": "io.orchis.tools.vertx.hazelcast.inmemory.InMemoryHazelcastVerticle" }
}

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, symmetric by default

secret

string

secret used for signing and parsing JWT

issuer

string

iss claim

algorithm

string

signing algorithm, HS256 by default

expiresIn

string

used to set exp claim, ISO-8601 format

toleranceInSeconds

int

skew time for parsing and nbf and iat in signing, 5 by default

prefix attribute is used to reference the service instance by other components.

sample configuration
{
  "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, asymmetric by default

serialNumber

string

kid header value

serviceId

string

iss claim, depracated, use spiffeId instead

instanceId

string

iid claim, depracated, use spiffeId instead

spiffeId

string

SPIFFE identifier

algorithm

string

signing algorithm, RS256 by default

expiresIn

string

used to set exp claim, ISO-8601 format

privateKey

string

Base64-encoded PKCS1 key

keyStore

json object

java.security.Keystore configuration, has path, password (optional), keyAlias and keyPassword attributes, used when privateKey is not set

toleranceInSeconds

int

skew time for parsing and nbf and iat in signing, 5 by default

prefix attribute is used to reference the service instance by other components.

sample configuration
{
  "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.

conf.json
{
  "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

meta-config.json
{
  "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"
      }
    }
  ]
}
config.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.json
{
  "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"
              }
            }
          ]
        }
      ]
    }
  ]
}