diff --git a/docs/manual/about.html b/docs/manual/about.html new file mode 100644 index 0000000000..8fa1fda860 --- /dev/null +++ b/docs/manual/about.html @@ -0,0 +1,348 @@ + + + + +About Otoroshi · Otoroshi + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+ + + +
+
+

About Otoroshi

+

At the beginning of 2017, we had the need to create a new environment to be able to create new “digital” products very quickly in an agile fashion at MAIF. Naturally we turned to PaaS solutions and chose the excellent Clever-Cloud product to run our apps.

+

We also chose that every feature team will have the freedom to choose its own technological stack to build its product. It was a nice move but it has also introduced some challenges in terms of homogeneity for traceability, security, logging, … because we did not want to force library usage in the products. We could have used something like Service Mesh Pattern but the deployement model of Clever-Cloud prevented us to do it.

+

The right solution was to use a reverse proxy or some kind of API Gateway able to provide tracability, logging, security with apikeys, quotas, DNS as a service locator, etc. We needed something easy to use, with a human friendly UI, a nice API to extends its features, true hot reconfiguration, able to generate internal events for third party usage. A couple of solutions were available at that time, but not one seems to fit our needs, there was always something missing, too complicated for our needs or not playing well with Clever-Cloud deployment model.

+

At some point, we tried to write a small prototype to explore what could be our dream reverse proxy. The design was very simple, there were some rough edges but every major feature needed was there waiting to be enhanced.

+

Otoroshi was born and we decided to move ahead with our hairy monster :)

+

Philosophy

+

Every OSS product build at MAIF like Izanami follow a common philosophy.

+
    +
  • the services or API provided should be technology agnostic.
  • +
  • http first: http is the right answer to the previous quote
  • +
  • api First: The UI is just another client of the api.
  • +
  • secured: The services exposed need authentication for both humans or machines
  • +
  • event based: The services should expose a way to get notified of what happened inside.
  • +
+ +
+
+ +
+
+ +
+
+ +
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + diff --git a/docs/manual/api.html b/docs/manual/api.html new file mode 100644 index 0000000000..5bc1dec27c --- /dev/null +++ b/docs/manual/api.html @@ -0,0 +1,349 @@ + + + + +Admin REST API · Otoroshi + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+ + + +
+
+

Admin REST API

+

Otoroshi provides a fully featured REST admin API to perform almost every operation possible in the Otoroshi dashboard. The Otoroshi dashbaord is just a regular consumer of the admin API.

+

Using the admin API, you can do whatever you want and enhance your Otoroshi instances with a lot of features that will feet your needs.

+

Otoroshi also provides some connectors that uses the Otoroshi admin API to automate Otorshi’s instances when used with stuff like containers orchestrators. For more informations about that, just go to the third party integrations chapter

+

Swagger descriptor

+

The Otoroshi admin API is described using OpenAPI format and is available at :

+

https://maif.github.io/otoroshi/manual/code/swagger.json

+

Every Otoroshi instance provides its own embedded OpenAPI descriptor at :

+

http://otoroshi.oto.tools:8080/api/swagger.json

+

Swagger documentation

+

You can read the OpenAPI descriptor in a more human friendly fashion using Swagger UI. The swagger UI documentation of the Otoroshi admin API is available at :

+

https://maif.github.io/otoroshi/swagger-ui/index.html

+

Every Otoroshi instance provides its own embedded OpenAPI descriptor at :

+

http://otoroshi.oto.tools:8080/api/swagger/ui

+

You can also read the swagger UI documentation of the Otoroshi admin API below :

+ +
+
+ +
+
+ +
+
+ +
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + diff --git a/docs/manual/archi.html b/docs/manual/archi.html new file mode 100644 index 0000000000..43d567739b --- /dev/null +++ b/docs/manual/archi.html @@ -0,0 +1,327 @@ + + + + +Architecture · Otoroshi + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+ + + +
+
+

Architecture

+

When we started the development of Otoroshi, we had several classical patterns in mind like Service gateway, Service locator, Circuit breakers, etc …

+

At start we thought about providing a bunch of librairies that would be included in each microservice or app to perform these tasks. But the more we were thinking about it, the more it was feeling weird, unagile, etc, it also prevented us to use any technical stack we wanted to use. So we decided to change our approach to something more universal.

+

We chose to make Otoroshi the central part of our microservices system, something between a reverse-proxy, a service gateway and a service locator where each call to a microservice (even from another microservice) must pass through Otoroshi. There are multiple benefits to do that, each call can be logged, audited, monitored, integrated with a circuit breaker, etc without imposing libraries and technical stack. Any service is exposed through its own domain and we rely only on DNS to handle the service location part. Any access to a service is secured by default with an api key and is supervised by a circuit breaker to avoid cascading failures.

+
+

Otoroshi tries to embrace our global philosophy by providing a full featured REST admin api, a gorgeous admin dashboard written in React that uses the api, by generating traffic events, alerts events, audit events that can be consumed by several channels. Otoroshi also supports a bunch of datastores to better match with different use cases.

+
+ +
+
+
+
+ +
+
+ +
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + diff --git a/docs/manual/cli.html b/docs/manual/cli.html new file mode 100644 index 0000000000..bb2fd934e0 --- /dev/null +++ b/docs/manual/cli.html @@ -0,0 +1,322 @@ + + + + +Rust CLI · Otoroshi + + + + + + + + + + + + + + + +
+
+ + + +
+ + + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + diff --git a/docs/manual/code/swagger.json b/docs/manual/code/swagger.json new file mode 100644 index 0000000000..37e48d0916 --- /dev/null +++ b/docs/manual/code/swagger.json @@ -0,0 +1,6458 @@ +{ + "swagger" : "2.0", + "info" : { + "version" : "1.4.18", + "title" : "Otoroshi Admin API", + "description" : "Admin API of the Otoroshi reverse proxy", + "contact" : { + "name" : "Otoroshi Team", + "email" : "oss@maif.fr" + }, + "license" : { + "name" : "Apache 2.0", + "url" : "http://www.apache.org/licenses/LICENSE-2.0.html" + } + }, + "tags" : [ { + "name" : "configuration", + "description" : "Everything about Otoroshi global configuration" + }, { + "name" : "import", + "description" : "Everything about Otoroshi import/export" + }, { + "name" : "templates", + "description" : "Everything about Otoroshi entities templates" + }, { + "name" : "environments", + "description" : "Everything about Otoroshi Environments" + }, { + "name" : "groups", + "description" : "Everything about Otoroshi service groups" + }, { + "name" : "apikeys", + "description" : "Everything about Otoroshi api keys" + }, { + "name" : "services", + "description" : "Everything about Otoroshi service descriptors" + }, { + "name" : "stats", + "description" : "Everything about Otoroshi stats" + }, { + "name" : "snowmonkey", + "description" : "Everything about Otoroshi Snow Monkey" + }, { + "name" : "health", + "description" : "Everything about Otoroshi health status" + }, { + "name" : "jwt-verifiers", + "description" : "Everything about Otoroshi global JWT token verifiers" + }, { + "name" : "auth-config", + "description" : "Everything about Otoroshi global auth. module config" + }, { + "name" : "scripts", + "description" : "Everything about Otoroshi request transformer scripts" + }, { + "name" : "certificates", + "description" : "Everything about Otoroshi SSL/TLS certificates" + }, { + "name" : "validation-authorities", + "description" : "Everything about Otoroshi validation authorities" + } ], + "externalDocs" : { + "description" : "Find out more about Otoroshi", + "url" : "https://maif.github.io/otoroshi/" + }, + "host" : "otoroshi-api.oto.tools", + "basePath" : "/", + "schemes" : [ "http" ], + "paths" : { + "/new/apikey" : { + "get" : { + "deprecated" : false, + "tags" : [ "templates" ], + "summary" : "Get a template of an Otoroshi Api Key", + "description" : "Get a template of an Otoroshi Api Key. The generated entity is not persisted", + "operationId" : "initiateApiKey", + "produces" : [ "application/json" ], + "parameters" : [ ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/ApiKey" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + } + }, + "/new/service" : { + "get" : { + "deprecated" : false, + "tags" : [ "templates" ], + "summary" : "Get a template of an Otoroshi service descriptor", + "description" : "Get a template of an Otoroshi service descriptor. The generated entity is not persisted", + "operationId" : "initiateService", + "produces" : [ "application/json" ], + "parameters" : [ ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/Service" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + } + }, + "/new/group" : { + "get" : { + "deprecated" : false, + "tags" : [ "templates" ], + "summary" : "Get a template of an Otoroshi service group", + "description" : "Get a template of an Otoroshi service group. The generated entity is not persisted", + "operationId" : "initiateServiceGroup", + "produces" : [ "application/json" ], + "parameters" : [ ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/Group" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + } + }, + "/lines" : { + "get" : { + "deprecated" : false, + "tags" : [ "environments" ], + "summary" : "Get all environments", + "description" : "Get all environments provided by the current Otoroshi instance", + "operationId" : "allLines", + "produces" : [ "application/json" ], + "parameters" : [ ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/Environment" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + } + }, + "/lines/{line}/services" : { + "get" : { + "deprecated" : false, + "tags" : [ "environments" ], + "summary" : "Get all services for an environment", + "description" : "Get all services for an environment provided by the current Otoroshi instance", + "operationId" : "servicesForALine", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "line", + "required" : true, + "type" : "string", + "description" : "The environment where to find services" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/Service" + } + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + } + }, + "/api/services/{serviceId}/apikeys/{clientId}/quotas" : { + "get" : { + "deprecated" : false, + "tags" : [ "apikeys" ], + "summary" : "Get the quota state of an api key", + "description" : "Get the quota state of an api key", + "operationId" : "apiKeyQuotas", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "serviceId", + "required" : true, + "type" : "string", + "description" : "The api key service id" + }, { + "in" : "path", + "name" : "clientId", + "required" : true, + "type" : "string", + "description" : "the api key id" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/Quotas" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "delete" : { + "deprecated" : false, + "tags" : [ "apikeys" ], + "summary" : "Reset the quota state of an api key", + "description" : "Reset the quota state of an api key", + "operationId" : "resetApiKeyQuotas", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "serviceId", + "required" : true, + "type" : "string", + "description" : "The api key service id" + }, { + "in" : "path", + "name" : "clientId", + "required" : true, + "type" : "string", + "description" : "the api key id" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/Quotas" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + } + }, + "/api/services/{serviceId}/apikeys/{clientId}/group" : { + "get" : { + "deprecated" : false, + "tags" : [ "apikeys" ], + "summary" : "Get the group of an api key", + "description" : "Get the group of an api key", + "operationId" : "apiKeyGroup", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "serviceId", + "required" : true, + "type" : "string", + "description" : "The api key service id" + }, { + "in" : "path", + "name" : "clientId", + "required" : true, + "type" : "string", + "description" : "the api key id" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/Group" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + } + }, + "/api/services/{serviceId}/apikeys/{clientId}" : { + "get" : { + "deprecated" : false, + "tags" : [ "apikeys" ], + "summary" : "Get an api key", + "description" : "Get an api key for a specified service descriptor", + "operationId" : "apiKey", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "serviceId", + "required" : true, + "type" : "string", + "description" : "The api key service id" + }, { + "in" : "path", + "name" : "clientId", + "required" : true, + "type" : "string", + "description" : "the api key id" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/ApiKey" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "put" : { + "deprecated" : false, + "tags" : [ "apikeys" ], + "summary" : "Update an api key", + "description" : "Update an api key for a specified service descriptor", + "operationId" : "updateApiKey", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "serviceId", + "required" : true, + "type" : "string", + "description" : "The api key service id" + }, { + "in" : "path", + "name" : "clientId", + "required" : true, + "type" : "string", + "description" : "the api key id" + }, { + "in" : "body", + "name" : "body", + "required" : true, + "schema" : { + "$ref" : "#/definitions/ApiKey" + }, + "description" : "The updated api key" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/ApiKey" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "patch" : { + "deprecated" : false, + "tags" : [ "apikeys" ], + "summary" : "Update an api key with a diff", + "description" : "Update an api key for a specified service descriptor with a diff", + "operationId" : "patchApiKey", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "serviceId", + "required" : true, + "type" : "string", + "description" : "The api key service id" + }, { + "in" : "path", + "name" : "clientId", + "required" : true, + "type" : "string", + "description" : "the api key id" + }, { + "in" : "body", + "name" : "body", + "required" : true, + "schema" : { + "$ref" : "#/definitions/Patch" + }, + "description" : "The patch for the api key" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/ApiKey" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "delete" : { + "deprecated" : false, + "tags" : [ "apikeys" ], + "summary" : "Delete an api key", + "description" : "Delete an api key for a specified service descriptor", + "operationId" : "deleteApiKey", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "serviceId", + "required" : true, + "type" : "string", + "description" : "The api key service id" + }, { + "in" : "path", + "name" : "clientId", + "required" : true, + "type" : "string", + "description" : "the api key id" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/Deleted" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + } + }, + "/api/services/{serviceId}/apikeys" : { + "get" : { + "deprecated" : false, + "tags" : [ "apikeys" ], + "summary" : "Get all api keys for the group of a service", + "description" : "Get all api keys for the group of a service", + "operationId" : "apiKeys", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "serviceId", + "required" : true, + "type" : "string", + "description" : "The api key service id" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/ApiKey" + } + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "post" : { + "deprecated" : false, + "tags" : [ "apikeys" ], + "summary" : "Create a new api key for a service", + "description" : "Create a new api key for a service", + "operationId" : "createApiKey", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "serviceId", + "required" : true, + "type" : "string", + "description" : "The api key service id" + }, { + "in" : "body", + "name" : "body", + "required" : true, + "schema" : { + "$ref" : "#/definitions/ApiKey" + }, + "description" : "The api key to create" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/ApiKey" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + } + }, + "/api/groups/{groupId}/apikeys/{clientId}/quotas" : { + "get" : { + "deprecated" : false, + "tags" : [ "apikeys" ], + "summary" : "Get the quota state of an api key", + "description" : "Get the quota state of an api key", + "operationId" : "apiKeyFromGroupQuotas", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "groupId", + "required" : true, + "type" : "string", + "description" : "The api key group id" + }, { + "in" : "path", + "name" : "clientId", + "required" : true, + "type" : "string", + "description" : "the api key id" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/Quotas" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "delete" : { + "deprecated" : false, + "tags" : [ "apikeys" ], + "summary" : "Reset the quota state of an api key", + "description" : "Reset the quota state of an api key", + "operationId" : "resetApiKeyFromGroupQuotas", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "groupId", + "required" : true, + "type" : "string", + "description" : "The api key group id" + }, { + "in" : "path", + "name" : "clientId", + "required" : true, + "type" : "string", + "description" : "the api key id" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/Quotas" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + } + }, + "/api/groups/{groupId}/apikeys/{clientId}" : { + "get" : { + "deprecated" : false, + "tags" : [ "apikeys" ], + "summary" : "Get an api key", + "description" : "Get an api key for a specified service group", + "operationId" : "apiKeyFromGroup", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "groupId", + "required" : true, + "type" : "string", + "description" : "The api key group id" + }, { + "in" : "path", + "name" : "clientId", + "required" : true, + "type" : "string", + "description" : "the api key id" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/ApiKey" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "put" : { + "deprecated" : false, + "tags" : [ "apikeys" ], + "summary" : "Update an api key", + "description" : "Update an api key for a specified service group", + "operationId" : "updateApiKeyFromGroup", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "groupId", + "required" : true, + "type" : "string", + "description" : "The api key group id" + }, { + "in" : "path", + "name" : "clientId", + "required" : true, + "type" : "string", + "description" : "the api key id" + }, { + "in" : "body", + "name" : "body", + "required" : true, + "schema" : { + "$ref" : "#/definitions/ApiKey" + }, + "description" : "The updated api key" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/ApiKey" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "patch" : { + "deprecated" : false, + "tags" : [ "apikeys" ], + "summary" : "Update an api key with a diff", + "description" : "Update an api key for a specified service descriptor with a diff", + "operationId" : "patchApiKeyFromGroup", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "groupId", + "required" : true, + "type" : "string", + "description" : "The api key group id" + }, { + "in" : "path", + "name" : "clientId", + "required" : true, + "type" : "string", + "description" : "the api key id" + }, { + "in" : "body", + "name" : "body", + "required" : true, + "schema" : { + "$ref" : "#/definitions/Patch" + }, + "description" : "The patch for the api key" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/ApiKey" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "delete" : { + "deprecated" : false, + "tags" : [ "apikeys" ], + "summary" : "Delete an api key", + "description" : "Delete an api key for a specified service group", + "operationId" : "deleteApiKeyFromGroup", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "groupId", + "required" : true, + "type" : "string", + "description" : "The api key group id" + }, { + "in" : "path", + "name" : "clientId", + "required" : true, + "type" : "string", + "description" : "the api key id" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/Deleted" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + } + }, + "/api/groups/{groupId}/apikeys" : { + "get" : { + "deprecated" : false, + "tags" : [ "apikeys" ], + "summary" : "Get all api keys for the group of a service", + "description" : "Get all api keys for the group of a service", + "operationId" : "apiKeysFromGroup", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "groupId", + "required" : true, + "type" : "string", + "description" : "The api key group id" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/ApiKey" + } + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "post" : { + "deprecated" : false, + "tags" : [ "apikeys" ], + "summary" : "Create a new api key for a group", + "description" : "Create a new api key for a group", + "operationId" : "createApiKeyFromGroup", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "groupId", + "required" : true, + "type" : "string", + "description" : "The api key group id" + }, { + "in" : "body", + "name" : "body", + "required" : true, + "schema" : { + "$ref" : "#/definitions/ApiKey" + }, + "description" : "The api key to create" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/ApiKey" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + } + }, + "/api/apikeys" : { + "get" : { + "deprecated" : false, + "tags" : [ "apikeys" ], + "summary" : "Get all api keys", + "description" : "Get all api keys", + "operationId" : "allApiKeys", + "produces" : [ "application/json" ], + "parameters" : [ ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/ApiKey" + } + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + } + }, + "/api/services/{serviceId}/template" : { + "get" : { + "deprecated" : false, + "tags" : [ "services" ], + "summary" : "Get a service descriptor error template", + "description" : "Get a service descriptor error template", + "operationId" : "serviceTemplate", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "serviceId", + "required" : true, + "type" : "string", + "description" : "The service id" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/ErrorTemplate" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "put" : { + "deprecated" : false, + "tags" : [ "services" ], + "summary" : "Update an error template to a service descriptor", + "description" : "Update an error template to a service descriptor", + "operationId" : "updateServiceTemplate", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "serviceId", + "required" : true, + "type" : "string", + "description" : "The service id" + }, { + "in" : "body", + "name" : "body", + "required" : true, + "schema" : { + "$ref" : "#/definitions/ErrorTemplate" + }, + "description" : "The updated service descriptor template" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/ErrorTemplate" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "post" : { + "deprecated" : false, + "tags" : [ "services" ], + "summary" : "Create a service descriptor error template", + "description" : "Update a service descriptor targets", + "operationId" : "createServiceTemplate", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "serviceId", + "required" : true, + "type" : "string", + "description" : "The service id" + }, { + "in" : "body", + "name" : "body", + "required" : true, + "schema" : { + "$ref" : "#/definitions/ErrorTemplate" + }, + "description" : "The patch for the service error template" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/ErrorTemplate" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "delete" : { + "deprecated" : false, + "tags" : [ "services" ], + "summary" : "Delete a service descriptor error template", + "description" : "Delete a service descriptor error template", + "operationId" : "deleteServiceTemplate", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "serviceId", + "required" : true, + "type" : "string", + "description" : "The service id" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/Deleted" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + } + }, + "/api/services/{serviceId}/targets" : { + "get" : { + "deprecated" : false, + "tags" : [ "services" ], + "summary" : "Get a service descriptor targets", + "description" : "Get a service descriptor targets", + "operationId" : "serviceTargets", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "serviceId", + "required" : true, + "type" : "string", + "description" : "The service id" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/Target" + } + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "post" : { + "deprecated" : false, + "tags" : [ "services" ], + "summary" : "Add a target to a service descriptor", + "description" : "Add a target to a service descriptor", + "operationId" : "serviceAddTarget", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "serviceId", + "required" : true, + "type" : "string", + "description" : "The service id" + }, { + "in" : "body", + "name" : "body", + "required" : true, + "schema" : { + "$ref" : "#/definitions/Target" + }, + "description" : "The updated service descriptor" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/Target" + } + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "patch" : { + "deprecated" : false, + "tags" : [ "services" ], + "summary" : "Update a service descriptor targets", + "description" : "Update a service descriptor targets", + "operationId" : "updateServiceTargets", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "serviceId", + "required" : true, + "type" : "string", + "description" : "The service id" + }, { + "in" : "body", + "name" : "body", + "required" : true, + "schema" : { + "$ref" : "#/definitions/Patch" + }, + "description" : "The patch for the service targets" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/Target" + } + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "delete" : { + "deprecated" : false, + "tags" : [ "services" ], + "summary" : "Delete a service descriptor target", + "description" : "Delete a service descriptor target", + "operationId" : "serviceDeleteTarget", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "serviceId", + "required" : true, + "type" : "string", + "description" : "The service id" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/Target" + } + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + } + }, + "/api/services/{serviceId}" : { + "get" : { + "deprecated" : false, + "tags" : [ "services" ], + "summary" : "Get a service descriptor", + "description" : "Get a service descriptor", + "operationId" : "service", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "serviceId", + "required" : true, + "type" : "string", + "description" : "The service id" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/Service" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "put" : { + "deprecated" : false, + "tags" : [ "services" ], + "summary" : "Update a service descriptor", + "description" : "Update a service descriptor", + "operationId" : "updateService", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "serviceId", + "required" : true, + "type" : "string", + "description" : "The service id" + }, { + "in" : "body", + "name" : "body", + "required" : true, + "schema" : { + "$ref" : "#/definitions/Service" + }, + "description" : "The updated service descriptor" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/Service" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "patch" : { + "deprecated" : false, + "tags" : [ "services" ], + "summary" : "Update a service descriptor with a diff", + "description" : "Update a service descriptor with a diff", + "operationId" : "patchService", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "serviceId", + "required" : true, + "type" : "string", + "description" : "The service id" + }, { + "in" : "body", + "name" : "body", + "required" : true, + "schema" : { + "$ref" : "#/definitions/Patch" + }, + "description" : "The patch for the service" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/Service" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "delete" : { + "deprecated" : false, + "tags" : [ "services" ], + "summary" : "Delete a service descriptor", + "description" : "Delete a service descriptor", + "operationId" : "deleteService", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "serviceId", + "required" : true, + "type" : "string", + "description" : "The service id" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/Deleted" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + } + }, + "/api/services" : { + "get" : { + "deprecated" : false, + "tags" : [ "services" ], + "summary" : "Get all services", + "description" : "Get all services", + "operationId" : "allServices", + "produces" : [ "application/json" ], + "parameters" : [ ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/Service" + } + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "post" : { + "deprecated" : false, + "tags" : [ "services" ], + "summary" : "Create a new service descriptor", + "description" : "Create a new service descriptor", + "operationId" : "createService", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "body", + "name" : "body", + "required" : true, + "schema" : { + "$ref" : "#/definitions/Service" + }, + "description" : "The service descriptor to create" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/Service" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + } + }, + "/api/groups/{serviceGroupId}/services" : { + "get" : { + "deprecated" : false, + "tags" : [ "services" ], + "summary" : "Get all services descriptor for a group", + "description" : "Get all services descriptor for a group", + "operationId" : "serviceGroupServices", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "serviceGroupId", + "required" : true, + "type" : "string", + "description" : "The service group id" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/ApiKey" + } + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + } + }, + "/api/groups/{serviceGroupId}" : { + "get" : { + "deprecated" : false, + "tags" : [ "groups" ], + "summary" : "Get a service group", + "description" : "Get a service group", + "operationId" : "serviceGroup", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "serviceGroupId", + "required" : true, + "type" : "string", + "description" : "The service group id" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/Group" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "put" : { + "deprecated" : false, + "tags" : [ "groups" ], + "summary" : "Update a service group", + "description" : "Update a service group", + "operationId" : "updateGroup", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "serviceGroupId", + "required" : true, + "type" : "string", + "description" : "The service group id" + }, { + "in" : "body", + "name" : "body", + "required" : true, + "schema" : { + "$ref" : "#/definitions/Group" + }, + "description" : "The updated service group" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/Group" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "patch" : { + "deprecated" : false, + "tags" : [ "groups" ], + "summary" : "Update a service group with a diff", + "description" : "Update a service group with a diff", + "operationId" : "patchGroup", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "serviceGroupId", + "required" : true, + "type" : "string", + "description" : "The service group id" + }, { + "in" : "body", + "name" : "body", + "required" : true, + "schema" : { + "$ref" : "#/definitions/Patch" + }, + "description" : "The patch for the service group" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/Group" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "delete" : { + "deprecated" : false, + "tags" : [ "groups" ], + "summary" : "Delete a service group", + "description" : "Delete a service group", + "operationId" : "deleteGroup", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "serviceGroupId", + "required" : true, + "type" : "string", + "description" : "The service group id" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/Deleted" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + } + }, + "/api/groups" : { + "get" : { + "deprecated" : false, + "tags" : [ "groups" ], + "summary" : "Get all service groups", + "description" : "Get all service groups", + "operationId" : "allServiceGroups", + "produces" : [ "application/json" ], + "parameters" : [ ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/Group" + } + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "post" : { + "deprecated" : false, + "tags" : [ "groups" ], + "summary" : "Create a new service group", + "description" : "Create a new service group", + "operationId" : "createGroup", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "body", + "name" : "body", + "required" : true, + "schema" : { + "$ref" : "#/definitions/Group" + }, + "description" : "The service group to create" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/Group" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + } + }, + "/api/verifiers" : { + "get" : { + "deprecated" : false, + "tags" : [ "jwt-verifiers" ], + "summary" : "Get one global JWT verifiers", + "description" : "Get one global JWT verifiers", + "operationId" : "findGlobalJwtVerifiersById", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "verifierId", + "required" : true, + "type" : "string", + "description" : "The jwt verifier id" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/GlobalJwtVerifier" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "delete" : { + "deprecated" : false, + "tags" : [ "jwt-verifiers" ], + "summary" : "Delete one global JWT verifiers", + "description" : "Delete one global JWT verifiers", + "operationId" : "deleteGlobalJwtVerifier", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "verifierId", + "required" : true, + "type" : "string", + "description" : "The jwt verifier id" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/Deleted" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "put" : { + "deprecated" : false, + "tags" : [ "jwt-verifiers" ], + "summary" : "Update one global JWT verifiers", + "description" : "Update one global JWT verifiers", + "operationId" : "updateGlobalJwtVerifier", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "verifierId", + "required" : true, + "type" : "string", + "description" : "The jwt verifier id" + }, { + "in" : "body", + "name" : "body", + "required" : true, + "schema" : { + "$ref" : "#/definitions/GlobalJwtVerifier" + }, + "description" : "The verifier to update" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/GlobalJwtVerifier" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "patch" : { + "deprecated" : false, + "tags" : [ "jwt-verifiers" ], + "summary" : "Update one global JWT verifiers", + "description" : "Update one global JWT verifiers", + "operationId" : "patchGlobalJwtVerifier", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "verifierId", + "required" : true, + "type" : "string", + "description" : "The jwt verifier id" + }, { + "in" : "body", + "name" : "body", + "required" : true, + "schema" : { + "$ref" : "#/definitions/Patch" + }, + "description" : "The verifier to update" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/GlobalJwtVerifier" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "post" : { + "deprecated" : false, + "tags" : [ "jwt-verifiers" ], + "summary" : "Create one global JWT verifiers", + "description" : "Create one global JWT verifiers", + "operationId" : "createGlobalJwtVerifier", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "body", + "name" : "body", + "required" : true, + "schema" : { + "$ref" : "#/definitions/GlobalJwtVerifier" + }, + "description" : "The verifier to create" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/GlobalJwtVerifier" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + } + }, + "/api/auths" : { + "get" : { + "deprecated" : false, + "tags" : [ "auth-config" ], + "summary" : "Get one global auth. module configs", + "description" : "Get one global auth. module configs", + "operationId" : "findGlobalAuthModuleById", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "id", + "required" : true, + "type" : "string", + "description" : "The auth. config id" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "oneOf" : [ { + "$ref" : "#/definitions/LdapAuthModuleConfig" + }, { + "$ref" : "#/definitions/InMemoryAuthModuleConfig" + }, { + "$ref" : "#/definitions/GenericOauth2ModuleConfig" + } ] + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "delete" : { + "deprecated" : false, + "tags" : [ "auth-config" ], + "summary" : "Delete one global auth. module config", + "description" : "Delete one global auth. module config", + "operationId" : "deleteGlobalAuthModule", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "id", + "required" : true, + "type" : "string", + "description" : "The auth. config id id" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/Deleted" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "put" : { + "deprecated" : false, + "tags" : [ "auth-config" ], + "summary" : "Update one global auth. module config", + "description" : "Update one global auth. module config", + "operationId" : "updateGlobalAuthModule", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "id", + "required" : true, + "type" : "string", + "description" : "The auth. config id" + }, { + "in" : "body", + "name" : "body", + "required" : true, + "schema" : { + "oneOf" : [ { + "$ref" : "#/definitions/LdapAuthModuleConfig" + }, { + "$ref" : "#/definitions/InMemoryAuthModuleConfig" + }, { + "$ref" : "#/definitions/GenericOauth2ModuleConfig" + } ] + }, + "description" : "The auth. config to update" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "oneOf" : [ { + "$ref" : "#/definitions/LdapAuthModuleConfig" + }, { + "$ref" : "#/definitions/InMemoryAuthModuleConfig" + }, { + "$ref" : "#/definitions/GenericOauth2ModuleConfig" + } ] + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "patch" : { + "deprecated" : false, + "tags" : [ "auth-config" ], + "summary" : "Update one global auth. module config", + "description" : "Update one global auth. module config", + "operationId" : "patchGlobalAuthModule", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "id", + "required" : true, + "type" : "string", + "description" : "The auth. config id" + }, { + "in" : "body", + "name" : "body", + "required" : true, + "schema" : { + "$ref" : "#/definitions/Patch" + }, + "description" : "The auth. config to update" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "oneOf" : [ { + "$ref" : "#/definitions/LdapAuthModuleConfig" + }, { + "$ref" : "#/definitions/InMemoryAuthModuleConfig" + }, { + "$ref" : "#/definitions/GenericOauth2ModuleConfig" + } ] + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "post" : { + "deprecated" : false, + "tags" : [ "auth-config" ], + "summary" : "Create one global auth. module config", + "description" : "Create one global auth. module config", + "operationId" : "createGlobalAuthModule", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "body", + "name" : "body", + "required" : true, + "schema" : { + "oneOf" : [ { + "$ref" : "#/definitions/LdapAuthModuleConfig" + }, { + "$ref" : "#/definitions/InMemoryAuthModuleConfig" + }, { + "$ref" : "#/definitions/GenericOauth2ModuleConfig" + } ] + }, + "description" : "The auth. config to create" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "oneOf" : [ { + "$ref" : "#/definitions/LdapAuthModuleConfig" + }, { + "$ref" : "#/definitions/InMemoryAuthModuleConfig" + }, { + "$ref" : "#/definitions/GenericOauth2ModuleConfig" + } ] + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + } + }, + "/api/scripts/_compile" : { + "post" : { + "deprecated" : false, + "tags" : [ "scripts" ], + "summary" : "Compile a script", + "description" : "Compile a script", + "operationId" : "compileScript", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "body", + "name" : "body", + "required" : true, + "schema" : { + "$ref" : "#/definitions/Script" + }, + "description" : "The script to compile" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/ScriptCompilationResult" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + } + }, + "/api/scripts/{scriptId}" : { + "get" : { + "deprecated" : false, + "tags" : [ "scripts" ], + "summary" : "Get a script", + "description" : "Get a script", + "operationId" : "findScriptById", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "scriptId", + "required" : true, + "type" : "string", + "description" : "The script id" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/Script" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "put" : { + "deprecated" : false, + "tags" : [ "scripts" ], + "summary" : "Update a script", + "description" : "Update a script", + "operationId" : "updateScript", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "scriptId", + "required" : true, + "type" : "string", + "description" : "The script id" + }, { + "in" : "body", + "name" : "body", + "required" : true, + "schema" : { + "$ref" : "#/definitions/Script" + }, + "description" : "The updated script" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/Script" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "patch" : { + "deprecated" : false, + "tags" : [ "scripts" ], + "summary" : "Update a script with a diff", + "description" : "Update a script with a diff", + "operationId" : "patchScript", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "scriptId", + "required" : true, + "type" : "string", + "description" : "The script id" + }, { + "in" : "body", + "name" : "body", + "required" : true, + "schema" : { + "$ref" : "#/definitions/Patch" + }, + "description" : "The patch for the script" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/Script" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "delete" : { + "deprecated" : false, + "tags" : [ "scripts" ], + "summary" : "Delete a script", + "description" : "Delete a script", + "operationId" : "deleteScript", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "scriptId", + "required" : true, + "type" : "string", + "description" : "The script id" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/Deleted" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + } + }, + "/api/scripts" : { + "get" : { + "deprecated" : false, + "tags" : [ "scripts" ], + "summary" : "Get all scripts", + "description" : "Get all scripts", + "operationId" : "findAllScripts", + "produces" : [ "application/json" ], + "parameters" : [ ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/Script" + } + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "post" : { + "deprecated" : false, + "tags" : [ "scripts" ], + "summary" : "Create a new script", + "description" : "Create a new script", + "operationId" : "createScript", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "body", + "name" : "body", + "required" : true, + "schema" : { + "$ref" : "#/definitions/Script" + }, + "description" : "The script to create" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/Script" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + } + }, + "/api/certificates" : { + "get" : { + "deprecated" : false, + "tags" : [ "certificates" ], + "summary" : "Get one certificate by id", + "description" : "Get one certificate by id", + "operationId" : "oneCert", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "id", + "required" : true, + "type" : "string", + "description" : "The auth. config id" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/Certificate" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "delete" : { + "deprecated" : false, + "tags" : [ "certificates" ], + "summary" : "Delete one certificate by id", + "description" : "Delete one certificate by id", + "operationId" : "deleteCert", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "id", + "required" : true, + "type" : "string", + "description" : "The certificate id" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/Deleted" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "put" : { + "deprecated" : false, + "tags" : [ "certificates" ], + "summary" : "Update one certificate by id", + "description" : "Update one certificate by id", + "operationId" : "putCert", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "id", + "required" : true, + "type" : "string", + "description" : "The certificate id" + }, { + "in" : "body", + "name" : "body", + "required" : true, + "schema" : { + "$ref" : "#/definitions/Certificate" + }, + "description" : "The certificate to update" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/Certificate" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "patch" : { + "deprecated" : false, + "tags" : [ "certificates" ], + "summary" : "Update one certificate by id", + "description" : "Update one certificate by id", + "operationId" : "patchCert", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "id", + "required" : true, + "type" : "string", + "description" : "The certificate id" + }, { + "in" : "body", + "name" : "body", + "required" : true, + "schema" : { + "$ref" : "#/definitions/Patch" + }, + "description" : "The certificate to update" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/Certificate" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "post" : { + "deprecated" : false, + "tags" : [ "certificates" ], + "summary" : "Create one certificate", + "description" : "Create one certificate", + "operationId" : "createCert", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "body", + "name" : "body", + "required" : true, + "schema" : { + "$ref" : "#/definitions/Certificate" + }, + "description" : "The certificate to create" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/Certificate" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + } + }, + "/api/client-validators" : { + "get" : { + "deprecated" : false, + "tags" : [ "validation-authorities" ], + "summary" : "Get one validation authorities by id", + "description" : "Get one validation authorities by id", + "operationId" : "findClientValidatorById", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "id", + "required" : true, + "type" : "string", + "description" : "The auth. config id" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/ValidationAuthority" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "delete" : { + "deprecated" : false, + "tags" : [ "validation-authorities" ], + "summary" : "Delete one validation authorities by id", + "description" : "Delete one validation authorities by id", + "operationId" : "deleteClientValidator", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "id", + "required" : true, + "type" : "string", + "description" : "The validation authorities id" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/Deleted" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "put" : { + "deprecated" : false, + "tags" : [ "validation-authorities" ], + "summary" : "Update one validation authorities by id", + "description" : "Update one validation authorities by id", + "operationId" : "updateClientValidator", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "id", + "required" : true, + "type" : "string", + "description" : "The validation authorities id" + }, { + "in" : "body", + "name" : "body", + "required" : true, + "schema" : { + "$ref" : "#/definitions/ValidationAuthority" + }, + "description" : "The validation authorities to update" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/ValidationAuthority" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "patch" : { + "deprecated" : false, + "tags" : [ "validation-authorities" ], + "summary" : "Update one validation authorities by id", + "description" : "Update one validation authorities by id", + "operationId" : "patchClientValidator", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "path", + "name" : "id", + "required" : true, + "type" : "string", + "description" : "The validation authorities id" + }, { + "in" : "body", + "name" : "body", + "required" : true, + "schema" : { + "$ref" : "#/definitions/Patch" + }, + "description" : "The validation authorities to update" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/ValidationAuthority" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "post" : { + "deprecated" : false, + "tags" : [ "validation-authorities" ], + "summary" : "Create one validation authorities", + "description" : "Create one validation authorities", + "operationId" : "createClientValidator", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "body", + "name" : "body", + "required" : true, + "schema" : { + "$ref" : "#/definitions/ValidationAuthority" + }, + "description" : "The validation authorities to create" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/ValidationAuthority" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + } + }, + "/api/snowmonkey/config" : { + "get" : { + "deprecated" : false, + "tags" : [ "snowmonkey" ], + "summary" : "Get current Snow Monkey config", + "description" : "Get current Snow Monkey config", + "operationId" : "getSnowMonkeyConfig", + "produces" : [ "application/json" ], + "parameters" : [ ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/SnowMonkeyConfig" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "put" : { + "deprecated" : false, + "tags" : [ "snowmonkey" ], + "summary" : "Update current Snow Monkey config", + "description" : "Update current Snow Monkey config", + "operationId" : "updateSnowMonkey", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "body", + "name" : "body", + "required" : true, + "schema" : { + "$ref" : "#/definitions/Group" + }, + "description" : "The service group to create" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/SnowMonkeyConfig" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "patch" : { + "deprecated" : false, + "tags" : [ "snowmonkey" ], + "summary" : "Update current Snow Monkey config", + "description" : "Update current Snow Monkey config", + "operationId" : "patchSnowMonkey", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "body", + "name" : "body", + "required" : true, + "schema" : { + "$ref" : "#/definitions/Group" + }, + "description" : "The service group to create" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/SnowMonkeyConfig" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + } + }, + "/api/snowmonkey/outages" : { + "get" : { + "deprecated" : false, + "tags" : [ "snowmonkey" ], + "summary" : "Get all current Snow Monkey ourages", + "description" : "Get all current Snow Monkey ourages", + "operationId" : "getSnowMonkeyOutages", + "produces" : [ "application/json" ], + "parameters" : [ ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/Outage" + } + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "delete" : { + "deprecated" : false, + "tags" : [ "snowmonkey" ], + "summary" : "Reset Snow Monkey Outages for the day", + "description" : "Reset Snow Monkey Outages for the day", + "operationId" : "resetSnowMonkey", + "produces" : [ "application/json" ], + "parameters" : [ ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/Done" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + } + }, + "/api/snowmonkey/_start" : { + "post" : { + "deprecated" : false, + "tags" : [ "snowmonkey" ], + "summary" : "Start the Snow Monkey", + "description" : "Start the Snow Monkey", + "operationId" : "startSnowMonkey", + "produces" : [ "application/json" ], + "parameters" : [ ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/Done" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + } + }, + "/api/snowmonkey/_stop" : { + "post" : { + "deprecated" : false, + "tags" : [ "snowmonkey" ], + "summary" : "Stop the Snow Monkey", + "description" : "Stop the Snow Monkey", + "operationId" : "stopSnowMonkey", + "produces" : [ "application/json" ], + "parameters" : [ ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/Done" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + } + }, + "/api/live/{id}" : { + "get" : { + "deprecated" : false, + "tags" : [ "stats" ], + "summary" : "Get live feed of otoroshi stats", + "description" : "Get live feed of global otoroshi stats (global) or for a service {id}", + "operationId" : "serviceLiveStats", + "produces" : [ "application/json", "text/event-stream" ], + "parameters" : [ { + "in" : "path", + "name" : "id", + "required" : true, + "type" : "string", + "description" : "The service id or global for otoroshi stats" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/Stats" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + } + }, + "/api/live" : { + "get" : { + "deprecated" : false, + "tags" : [ "stats" ], + "summary" : "Get global otoroshi stats", + "description" : "Get global otoroshi stats", + "operationId" : "globalLiveStats", + "produces" : [ "application/json" ], + "parameters" : [ ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/Stats" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + } + }, + "/api/globalconfig" : { + "get" : { + "deprecated" : false, + "tags" : [ "configuration" ], + "summary" : "Get the full configuration of Otoroshi", + "description" : "Get the full configuration of Otoroshi", + "operationId" : "globalConfig", + "produces" : [ "application/json" ], + "parameters" : [ ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/GlobalConfig" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "put" : { + "deprecated" : false, + "tags" : [ "configuration" ], + "summary" : "Update the global configuration", + "description" : "Update the global configuration", + "operationId" : "putGlobalConfig", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "body", + "name" : "body", + "required" : true, + "schema" : { + "$ref" : "#/definitions/GlobalConfig" + }, + "description" : "The updated global config" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/GlobalConfig" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "patch" : { + "deprecated" : false, + "tags" : [ "configuration" ], + "summary" : "Update the global configuration with a diff", + "description" : "Update the global configuration with a diff", + "operationId" : "patchGlobalConfig", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "body", + "name" : "body", + "required" : true, + "schema" : { + "$ref" : "#/definitions/Patch" + }, + "description" : "The updated global config as patch" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/GlobalConfig" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + } + }, + "/api/otoroshi.json" : { + "get" : { + "deprecated" : false, + "tags" : [ "import" ], + "summary" : "Export the full state of Otoroshi", + "description" : "Export the full state of Otoroshi", + "operationId" : "fullExport", + "produces" : [ "application/json" ], + "parameters" : [ ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/ImportExport" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + }, + "post" : { + "deprecated" : false, + "tags" : [ "import" ], + "summary" : "Import the full state of Otoroshi", + "description" : "Import the full state of Otoroshi", + "operationId" : "fullImport", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "body", + "name" : "body", + "required" : true, + "schema" : { + "$ref" : "#/definitions/ImportExport" + }, + "description" : "The full export" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/Done" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + } + }, + "/api/import" : { + "post" : { + "deprecated" : false, + "tags" : [ "import" ], + "summary" : "Import the full state of Otoroshi as a file", + "description" : "Import the full state of Otoroshi as a file", + "operationId" : "fullImportFromFile", + "produces" : [ "application/json" ], + "parameters" : [ { + "in" : "body", + "name" : "body", + "required" : true, + "schema" : { + "$ref" : "#/definitions/ImportExport" + }, + "description" : "The full export" + } ], + "responses" : { + "401" : { + "description" : "You have to provide an Api Key. Api Key can be passed with 'Otoroshi-Client-Id' and 'Otoroshi-Client-Secret' headers, or use basic http authentication" + }, + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/Done" + } + } + }, + "security" : [ { + "otoroshi_auth" : [ ] + } ] + } + }, + "/health" : { + "get" : { + "deprecated" : false, + "tags" : [ "health" ], + "summary" : "Return current Otoroshi health", + "description" : "Import the full state of Otoroshi as a file", + "operationId" : "health", + "produces" : [ "application/json" ], + "parameters" : [ ], + "responses" : { + "400" : { + "description" : "Bad resource format. Take another look to the swagger, or open an issue :)" + }, + "404" : { + "description" : "Resource not found or does not exist" + }, + "200" : { + "description" : "Successful operation", + "schema" : { + "$ref" : "#/definitions/OtoroshiHealth" + } + } + } + } + } + }, + "securityDefinitions" : { + "otoroshi_auth" : { + "type" : "basic" + } + }, + "definitions" : { + "ApiKey" : { + "description" : "An Otoroshi Api Key. An Api Key is defined for a group of services to allow usage of the same Api Key for multiple services.", + "type" : "object", + "required" : [ "clientId", "clientSecret", "clientName", "authorizedGroup", "enabled" ], + "properties" : { + "clientId" : { + "description" : "The unique id of the Api Key. Usually 16 random alpha numerical characters, but can be anything", + "type" : "string", + "example" : "a string value" + }, + "clientSecret" : { + "description" : "The secret of the Api Key. Usually 64 random alpha numerical characters, but can be anything", + "type" : "string", + "example" : "a string value" + }, + "clientName" : { + "description" : "The name of the api key, for humans ;-)", + "type" : "string", + "example" : "a string value" + }, + "authorizedGroup" : { + "description" : "The group id on which the key is authorized", + "type" : "string", + "example" : "a string value" + }, + "enabled" : { + "description" : "Whether or not the key is enabled. If disabled, resources won't be available to calls using this key", + "type" : "boolean", + "example" : true + }, + "throttlingQuota" : { + "format" : "int64", + "description" : "Authorized number of calls per second, measured on 10 seconds", + "type" : "integer", + "example" : 123 + }, + "dailyQuota" : { + "format" : "int64", + "description" : "Authorized number of calls per day", + "type" : "integer", + "example" : 123 + }, + "monthlyQuota" : { + "format" : "int64", + "description" : "Authorized number of calls per month", + "type" : "integer", + "example" : 123 + }, + "metadata" : { + "description" : "Bunch of metadata for the key", + "additionalProperties" : { + "type" : "string" + }, + "type" : "object", + "example" : { + "key" : "value" + } + } + } + }, + "Auth0Config" : { + "description" : "Configuration for Auth0 domain", + "type" : "object", + "required" : [ "clientId", "clientSecret", "domain", "callbackUrl" ], + "properties" : { + "clientId" : { + "description" : "Auth0 client id", + "type" : "string", + "example" : "a string value" + }, + "clientSecret" : { + "description" : "Auth0 client secret", + "type" : "string", + "example" : "a string value" + }, + "domain" : { + "description" : "Auth0 domain", + "type" : "string", + "example" : "a string value" + }, + "callbackUrl" : { + "description" : "Auth0 callback URL", + "type" : "string", + "example" : "a string value" + } + } + }, + "Canary" : { + "description" : "The configuration of the canary mode for a service descriptor", + "type" : "object", + "required" : [ "enabled", "traffic", "targets", "root" ], + "properties" : { + "enabled" : { + "description" : "Use canary mode for this service", + "type" : "boolean", + "example" : true + }, + "traffic" : { + "format" : "int32", + "description" : "Ratio of traffic that will be sent to canary targets.", + "type" : "integer", + "example" : 123123 + }, + "targets" : { + "description" : "The list of target that Otoroshi will proxy and expose through the subdomain defined before. Otoroshi will do round-robin load balancing between all those targets with circuit breaker mecanism to avoid cascading failures", + "type" : "array", + "items" : { + "$ref" : "#/definitions/Target" + } + }, + "root" : { + "description" : "Otoroshi will append this root to any target choosen. If the specified root is '/api/foo', then a request to https://yyyyyyy/bar will actually hit https://xxxxxxxxx/api/foo/bar", + "type" : "string", + "example" : "a string value" + } + } + }, + "CleverSettings" : { + "description" : "Configuration for CleverCloud client", + "type" : "object", + "required" : [ "consumerKey", "consumerSecret", "token", "secret", "orgaId" ], + "properties" : { + "consumerKey" : { + "description" : "CleverCloud consumer key", + "type" : "string", + "example" : "a string value" + }, + "consumerSecret" : { + "description" : "CleverCloud consumer token", + "type" : "string", + "example" : "a string value" + }, + "token" : { + "description" : "CleverCloud oauth token", + "type" : "string", + "example" : "a string value" + }, + "secret" : { + "description" : "CleverCloud oauth secret", + "type" : "string", + "example" : "a string value" + }, + "orgaId" : { + "description" : "CleverCloud organization id", + "type" : "string", + "example" : "a string value" + } + } + }, + "ClientConfig" : { + "description" : "The configuration of the circuit breaker for a service descriptor", + "type" : "object", + "required" : [ "useCircuitBreaker", "retries", "maxErrors", "retryInitialDelay", "backoffFactor", "callTimeout", "globalTimeout", "sampleInterval" ], + "properties" : { + "useCircuitBreaker" : { + "description" : "Use a circuit breaker to avoid cascading failure when calling chains of services. Highly recommended !", + "type" : "boolean", + "example" : true + }, + "retries" : { + "format" : "int32", + "description" : "Specify how many times the client will try to fetch the result of the request after an error before giving up.", + "type" : "integer", + "example" : 123123 + }, + "maxErrors" : { + "format" : "int32", + "description" : "Specify how many errors can pass before opening the circuit breaker", + "type" : "integer", + "example" : 123123 + }, + "retryInitialDelay" : { + "format" : "int32", + "description" : "Specify the delay between two retries. Each retry, the delay is multiplied by the backoff factor", + "type" : "integer", + "example" : 123123 + }, + "backoffFactor" : { + "format" : "int32", + "description" : "Specify the factor to multiply the delay for each retry", + "type" : "integer", + "example" : 123123 + }, + "callTimeout" : { + "format" : "int32", + "description" : "Specify how long each call should last at most in milliseconds", + "type" : "integer", + "example" : 123123 + }, + "globalTimeout" : { + "format" : "int32", + "description" : "Specify how long the global call (with retries) should last at most in milliseconds", + "type" : "integer", + "example" : 123123 + }, + "sampleInterval" : { + "format" : "int32", + "description" : "Specify the sliding window time for the circuit breaker in milliseconds, after this time, error count will be reseted", + "type" : "integer", + "example" : 123123 + } + } + }, + "Deleted" : { + "type" : "object", + "required" : [ "deleted" ], + "properties" : { + "deleted" : { + "type" : "boolean", + "example" : true + } + } + }, + "Done" : { + "type" : "object", + "required" : [ "done" ], + "properties" : { + "done" : { + "type" : "boolean", + "example" : true + } + } + }, + "Environment" : { + "type" : "string", + "example" : "prod", + "description" : "The name of the environment for service descriptors" + }, + "ErrorTemplate" : { + "description" : "Error templates for a service descriptor", + "type" : "object", + "required" : [ "serviceId", "template40x", "template50x", "templateBuild", "templateMaintenance", "messages" ], + "properties" : { + "serviceId" : { + "description" : "The Id of the service for which the error template is enabled", + "type" : "string", + "example" : "a string value" + }, + "template40x" : { + "description" : "The html template for 40x errors", + "type" : "string", + "example" : "a string value" + }, + "template50x" : { + "description" : "The html template for 50x errors", + "type" : "string", + "example" : "a string value" + }, + "templateBuild" : { + "description" : "The html template for build page", + "type" : "string", + "example" : "a string value" + }, + "templateMaintenance" : { + "description" : "The html template for maintenance page", + "type" : "string", + "example" : "a string value" + }, + "messages" : { + "description" : "Map for custom messages", + "additionalProperties" : { + "type" : "string" + }, + "type" : "object", + "example" : { + "key" : "value" + } + } + } + }, + "ExposedApi" : { + "description" : "The Open API configuration for your service (if one)", + "type" : "object", + "required" : [ "exposeApi" ], + "properties" : { + "exposeApi" : { + "description" : "Whether or not the current service expose an API with an Open API descriptor", + "type" : "boolean", + "example" : true + }, + "openApiDescriptorUrl" : { + "format" : "uri", + "description" : "The URL of the Open API descriptor", + "type" : "string", + "example" : "http://www.google.com" + } + } + }, + "GlobalConfig" : { + "type" : "object", + "required" : [ "streamEntityOnly", "autoLinkToDefaultGroup", "limitConcurrentRequests", "maxConcurrentRequests", "useCircuitBreakers", "apiReadOnly", "u2fLoginOnly", "ipFiltering", "throttlingQuota", "perIpThrottlingQuota", "analyticsWebhooks", "alertsWebhooks", "alertsEmails", "endlessIpAddresses" ], + "description" : "The global config object of Otoroshi, used to customize settings of the current Otoroshi instance", + "properties" : { + "lines" : { + "description" : "Possibles lines for Otoroshi", + "type" : "array", + "items" : { + "type" : "string", + "example" : "a string value" + } + }, + "streamEntityOnly" : { + "description" : "HTTP will be streamed only. Doesn't work with old browsers", + "type" : "boolean", + "example" : true + }, + "autoLinkToDefaultGroup" : { + "description" : "If not defined, every new service descriptor will be added to the default group", + "type" : "boolean", + "example" : true + }, + "limitConcurrentRequests" : { + "description" : "If enabled, Otoroshi will reject new request if too much at the same time", + "type" : "boolean", + "example" : true + }, + "maxConcurrentRequests" : { + "format" : "int64", + "description" : "The number of authorized request processed at the same time", + "type" : "integer", + "example" : 123 + }, + "maxHttp10ResponseSize" : { + "format" : "int64", + "description" : "The max size in bytes of an HTTP 1.0 response", + "type" : "integer", + "example" : 123 + }, + "useCircuitBreakers" : { + "description" : "If enabled, services will be authorized to use circuit breakers", + "type" : "boolean", + "example" : true + }, + "apiReadOnly" : { + "description" : "If enabled, Admin API won't be able to write/update/delete entities", + "type" : "boolean", + "example" : true + }, + "u2fLoginOnly" : { + "description" : "If enabled, login to backoffice through Auth0 will be disabled", + "type" : "boolean", + "example" : true + }, + "ipFiltering" : { + "$ref" : "#/definitions/IpFiltering" + }, + "throttlingQuota" : { + "format" : "int64", + "description" : "Authorized number of calls per second globally, measured on 10 seconds", + "type" : "integer", + "example" : 123 + }, + "perIpThrottlingQuota" : { + "format" : "int64", + "description" : "Authorized number of calls per second globally per IP address, measured on 10 seconds", + "type" : "integer", + "example" : 123 + }, + "elasticWritesConfigs" : { + "description" : "Configs. for Elastic writes", + "type" : "array", + "items" : { + "$ref" : "#/definitions/ElasticConfig" + } + }, + "elasticReadsConfig" : { + "description" : "Config. for elastic reads", + "$ref" : "#/definitions/ElasticConfig" + }, + "analyticsWebhooks" : { + "description" : "Webhook that will receive all internal Otoroshi events", + "type" : "array", + "items" : { + "$ref" : "#/definitions/Webhook" + } + }, + "alertsWebhooks" : { + "description" : "Webhook that will receive all Otoroshi alert events", + "type" : "array", + "items" : { + "$ref" : "#/definitions/Webhook" + } + }, + "alertsEmails" : { + "description" : "Email addresses that will receive all Otoroshi alert events", + "type" : "array", + "items" : { + "type" : "string", + "format" : "email", + "example" : "admin@otoroshi.io" + } + }, + "endlessIpAddresses" : { + "description" : "IP addresses for which any request to Otoroshi will respond with 128 Gb of zeros", + "type" : "array", + "items" : { + "type" : "string", + "format" : "ipv4", + "example" : "192.192.192.192" + } + }, + "middleFingers" : { + "description" : "Use middle finger emoji as a response character for endless HTTP responses", + "type" : "boolean", + "example" : true + }, + "maxLogsSize" : { + "format" : "int32", + "description" : "Number of events kept locally", + "type" : "integer", + "example" : 123123 + }, + "cleverSettings" : { + "description" : "Optional CleverCloud configuration", + "$ref" : "#/definitions/CleverSettings" + }, + "mailerSettings" : { + "description" : "Optional mailer configuration", + "$ref" : "#/definitions/MailerSettings" + }, + "backofficeAuth0Config" : { + "description" : "Optional configuration for the backoffice Auth0 domain", + "$ref" : "#/definitions/Auth0Config" + }, + "privateAppsAuth0Config" : { + "description" : "Optional configuration for the private apps Auth0 domain", + "$ref" : "#/definitions/Auth0Config" + } + } + }, + "Group" : { + "description" : "An Otoroshi service group is just a group of service descriptor. It is useful to be able to define Api Keys for the whole group", + "type" : "object", + "required" : [ "id", "name" ], + "properties" : { + "id" : { + "description" : "The unique id of the group. Usually 64 random alpha numerical characters, but can be anything", + "type" : "string", + "example" : "a string value" + }, + "name" : { + "description" : "The name of the group", + "type" : "string", + "example" : "a string value" + }, + "description" : { + "description" : "The descriptoin of the group", + "type" : "string", + "example" : "a string value" + } + } + }, + "HealthCheck" : { + "description" : "The configuration for checking health of a service. Otoroshi will perform GET call on the URL to check if the service is still alive", + "type" : "object", + "required" : [ "enabled" ], + "properties" : { + "enabled" : { + "description" : "Whether or not healthcheck is enabled on the current service descriptor", + "type" : "boolean", + "example" : true + }, + "url" : { + "format" : "uri", + "description" : "The URL to check", + "type" : "string", + "example" : "http://www.google.com" + } + } + }, + "OtoroshiHealth" : { + "description" : "The structure that represent current Otoroshi health", + "type" : "object", + "required" : [ "label", "otoroshi", "datastore" ], + "properties" : { + "otoroshi" : { + "type" : "string", + "enum" : [ "healthy", "unhealthy", "down" ] + }, + "datastore" : { + "type" : "string", + "enum" : [ "healthy", "unhealthy", "unreachable" ] + } + } + }, + "ImportExport" : { + "description" : "The structure that can be imported to or exported from Otoroshi. It represent the memory state of Otoroshi", + "type" : "object", + "required" : [ "label", "dateRaw", "date", "stats", "config", "admins", "simpleAdmins", "serviceGroups", "apiKeys", "serviceDescriptors", "errorTemplates" ], + "properties" : { + "label" : { + "type" : "string", + "example" : "a string value" + }, + "dateRaw" : { + "type" : "integer", + "format" : "int64", + "example" : 123 + }, + "date" : { + "type" : "string", + "format" : "date-time", + "example" : "2017-07-21T17:32:28Z" + }, + "stats" : { + "description" : "Current global stats at the time of export", + "$ref" : "#/definitions/ImportExportStats" + }, + "config" : { + "description" : "Current global config at the time of export", + "$ref" : "#/definitions/GlobalConfig" + }, + "appConfig" : { + "description" : "Current env variables at the time of export", + "additionalProperties" : { + "type" : "string" + }, + "type" : "object", + "example" : { + "key" : "value" + } + }, + "admins" : { + "description" : "Current U2F admin at the time of export", + "type" : "array", + "items" : { + "description" : "Administrator using FIDO U2F device to access Otoroshi", + "type" : "object", + "required" : [ "username", "label", "password", "createdAt", "registration" ], + "properties" : { + "username" : { + "description" : "The email address of the user", + "type" : "string", + "example" : "a string value" + }, + "label" : { + "description" : "The label for the user", + "type" : "string", + "example" : "a string value" + }, + "password" : { + "description" : "The hashed password of the user", + "type" : "string", + "example" : "a string value" + }, + "createdAt" : { + "format" : "int64", + "description" : "The creation date of the user", + "type" : "integer", + "example" : 123 + }, + "registration" : { + "description" : "The U2F registration slug", + "additionalProperties" : { + "type" : "string" + }, + "type" : "object", + "example" : { + "key" : "value" + } + } + } + } + }, + "simpleAdmins" : { + "description" : "Current simple admins at the time of export", + "type" : "array", + "items" : { + "description" : "Administrator using just login/password tuple to access Otoroshi", + "type" : "object", + "required" : [ "username", "label", "password", "createdAt" ], + "properties" : { + "username" : { + "description" : "The email address of the user", + "type" : "string", + "example" : "a string value" + }, + "label" : { + "description" : "The label for the user", + "type" : "string", + "example" : "a string value" + }, + "password" : { + "description" : "The hashed password of the user", + "type" : "string", + "example" : "a string value" + }, + "createdAt" : { + "format" : "int64", + "description" : "The creation date of the user", + "type" : "integer", + "example" : 123 + } + } + } + }, + "serviceGroups" : { + "description" : "Current service groups at the time of export", + "type" : "array", + "items" : { + "description" : "An Otoroshi service group is just a group of service descriptor. It is useful to be able to define Api Keys for the whole group", + "type" : "object", + "required" : [ "id", "name" ], + "properties" : { + "id" : { + "description" : "The unique id of the group. Usually 64 random alpha numerical characters, but can be anything", + "type" : "string", + "example" : "a string value" + }, + "name" : { + "description" : "The name of the group", + "type" : "string", + "example" : "a string value" + }, + "description" : { + "description" : "The descriptoin of the group", + "type" : "string", + "example" : "a string value" + } + } + } + }, + "apiKeys" : { + "description" : "Current apik keys at the time of export", + "type" : "array", + "items" : { + "description" : "An Otoroshi Api Key. An Api Key is defined for a group of services to allow usage of the same Api Key for multiple services.", + "type" : "object", + "required" : [ "clientId", "clientSecret", "clientName", "authorizedGroup", "enabled" ], + "properties" : { + "clientId" : { + "description" : "The unique id of the Api Key. Usually 16 random alpha numerical characters, but can be anything", + "type" : "string", + "example" : "a string value" + }, + "clientSecret" : { + "description" : "The secret of the Api Key. Usually 64 random alpha numerical characters, but can be anything", + "type" : "string", + "example" : "a string value" + }, + "clientName" : { + "description" : "The name of the api key, for humans ;-)", + "type" : "string", + "example" : "a string value" + }, + "authorizedGroup" : { + "description" : "The group id on which the key is authorized", + "type" : "string", + "example" : "a string value" + }, + "enabled" : { + "description" : "Whether or not the key is enabled. If disabled, resources won't be available to calls using this key", + "type" : "boolean", + "example" : true + }, + "throttlingQuota" : { + "format" : "int64", + "description" : "Authorized number of calls per second, measured on 10 seconds", + "type" : "integer", + "example" : 123 + }, + "dailyQuota" : { + "format" : "int64", + "description" : "Authorized number of calls per day", + "type" : "integer", + "example" : 123 + }, + "monthlyQuota" : { + "format" : "int64", + "description" : "Authorized number of calls per month", + "type" : "integer", + "example" : 123 + }, + "metadata" : { + "description" : "Bunch of metadata for the key", + "additionalProperties" : { + "type" : "string" + }, + "type" : "object", + "example" : { + "key" : "value" + } + } + } + } + }, + "serviceDescriptors" : { + "description" : "Current service descriptors at the time of export", + "type" : "array", + "items" : { + "description" : "An otoroshi service descriptor. Represent a forward HTTP call on a domain to another location with some optional api management mecanism", + "type" : "object", + "required" : [ "id", "groupId", "name", "env", "domain", "subdomain", "targets", "root", "enabled", "privateApp", "forceHttps", "maintenanceMode", "buildMode", "enforceSecureCommunication" ], + "properties" : { + "id" : { + "format" : "uuid", + "description" : "A unique random string to identify your service", + "type" : "string", + "example" : "110e8400-e29b-11d4-a716-446655440000" + }, + "groupId" : { + "description" : "Each service descriptor is attached to a group. A group can have one or more services. Each API key is linked to a group and allow access to every service in the group", + "type" : "string", + "example" : "a string value" + }, + "name" : { + "description" : "The name of your service. Only for debug and human readability purposes", + "type" : "string", + "example" : "a string value" + }, + "env" : { + "description" : "The line on which the service is available. Based on that value, the name of the line will be appended to the subdomain. For line prod, nothing will be appended. For example, if the subdomain is 'foo' and line is 'preprod', then the exposed service will be available at 'foo.preprod.mydomain'", + "type" : "string", + "example" : "a string value" + }, + "domain" : { + "description" : "The domain on which the service is available.", + "type" : "string", + "example" : "a string value" + }, + "subdomain" : { + "description" : "The subdomain on which the service is available", + "type" : "string", + "example" : "a string value" + }, + "targets" : { + "description" : "The list of target that Otoroshi will proxy and expose through the subdomain defined before. Otoroshi will do round-robin load balancing between all those targets with circuit breaker mecanism to avoid cascading failures", + "type" : "array", + "items" : { + "$ref" : "#/definitions/Target" + } + }, + "root" : { + "description" : "Otoroshi will append this root to any target choosen. If the specified root is '/api/foo', then a request to https://yyyyyyy/bar will actually hit https://xxxxxxxxx/api/foo/bar", + "type" : "string", + "example" : "a string value" + }, + "matchingRoot" : { + "description" : "The root path on which the service is available", + "type" : "string", + "example" : "a string value" + }, + "localHost" : { + "description" : "The host used localy, mainly localhost:xxxx", + "type" : "string", + "example" : "a string value" + }, + "localScheme" : { + "description" : "The scheme used localy, mainly http", + "type" : "string", + "example" : "a string value" + }, + "redirectToLocal" : { + "description" : "If you work locally with Otoroshi, you may want to use that feature to redirect one particuliar service to a local host. For example, you can relocate https://foo.preprod.bar.com to http://localhost:8080 to make some tests", + "type" : "boolean", + "example" : true + }, + "enabled" : { + "description" : "Activate or deactivate your service. Once disabled, users will get an error page saying the service does not exist", + "type" : "boolean", + "example" : true + }, + "userFacing" : { + "description" : "The fact that this service will be seen by users and cannot be impacted by the Snow Monkey", + "type" : "boolean", + "example" : true + }, + "privateApp" : { + "description" : "When enabled, user will be allowed to use the service (UI) only if they are registered users of the private apps domain", + "type" : "boolean", + "example" : true + }, + "forceHttps" : { + "description" : "Will force redirection to https:// if not present", + "type" : "boolean", + "example" : true + }, + "maintenanceMode" : { + "description" : "Display a maintainance page when a user try to use the service", + "type" : "boolean", + "example" : true + }, + "buildMode" : { + "description" : "Display a construction page when a user try to use the service", + "type" : "boolean", + "example" : true + }, + "enforceSecureCommunication" : { + "description" : "When enabled, Otoroshi will try to exchange headers with downstream service to ensure no one else can use the service from outside", + "type" : "boolean", + "example" : true + }, + "sendOtoroshiHeadersBack" : { + "description" : "When enabled, Otoroshi will send headers to consumer like request id, client latency, overhead, etc ...", + "type" : "boolean", + "example" : true + }, + "xForwardedHeaders" : { + "description" : "Send X-Forwarded-* headers", + "type" : "boolean", + "example" : true + }, + "overrideHost" : { + "description" : "Host header will be overriden with Host of the target", + "type" : "boolean", + "example" : true + }, + "secComExcludedPatterns" : { + "description" : "URI patterns excluded from secured communications", + "type" : "array", + "items" : { + "type" : "string", + "example" : "a string value" + } + }, + "publicPatterns" : { + "description" : "By default, every services are private only and you'll need an API key to access it. However, if you want to expose a public UI, you can define one or more public patterns (regex) to allow access to anybody. For example if you want to allow anybody on any URL, just use '/.*'", + "type" : "array", + "items" : { + "type" : "string", + "example" : "a string value" + } + }, + "privatePatterns" : { + "description" : "If you define a public pattern that is a little bit too much, you can make some of public URL private again", + "type" : "array", + "items" : { + "type" : "string", + "example" : "a string value" + } + }, + "ipFiltering" : { + "$ref" : "#/definitions/IpFiltering" + }, + "api" : { + "$ref" : "#/definitions/ExposedApi" + }, + "healthCheck" : { + "$ref" : "#/definitions/HealthCheck" + }, + "clientConfig" : { + "$ref" : "#/definitions/ClientConfig" + }, + "Canary" : { + "$ref" : "#/definitions/Canary" + }, + "statsdConfig" : { + "$ref" : "#/definitions/StatsdConfig" + }, + "chaosConfig" : { + "$ref" : "#/definitions/ChaosConfig" + }, + "jwtVerifier" : { + "oneOf" : [ { + "$ref" : "#/definitions/LocalJwtVerifier" + }, { + "$ref" : "#/definitions/RefJwtVerifier" + } ] + }, + "secComSettings" : { + "oneOf" : [ { + "$ref" : "#/definitions/HSAlgoSettings" + }, { + "$ref" : "#/definitions/RSAlgoSettings" + }, { + "$ref" : "#/definitions/ESAlgoSettings" + }, { + "$ref" : "#/definitions/JWKSAlgoSettings" + } ] + }, + "metadata" : { + "description" : "Just a bunch of random properties", + "additionalProperties" : { + "type" : "string" + }, + "type" : "object", + "example" : { + "key" : "value" + } + }, + "matchingHeaders" : { + "description" : "Specify headers that MUST be present on client request to route it. Useful to implement versioning", + "additionalProperties" : { + "type" : "string" + }, + "type" : "object", + "example" : { + "key" : "value" + } + }, + "additionalHeaders" : { + "description" : "Specify headers that will be added to each client request. Useful to add authentication", + "additionalProperties" : { + "type" : "string" + }, + "type" : "object", + "example" : { + "key" : "value" + } + }, + "authConfigRef" : { + "description" : "A reference to a global auth module config", + "type" : "string", + "example" : "a string value" + }, + "transformerRef" : { + "description" : "A reference to a request transformer", + "type" : "string", + "example" : "a string value" + }, + "clientValidatorRef" : { + "description" : "A reference to validation authority", + "type" : "string", + "example" : "a string value" + }, + "cors" : { + "$ref" : "#/definitions/CorsSettings" + }, + "redirection" : { + "$ref" : "#/definitions/RedirectionSettings" + }, + "gzip" : { + "$ref" : "#/definitions/Gzip" + }, + "headersVerification" : { + "description" : "Specify headers that will be verified after routing.", + "additionalProperties" : { + "type" : "string" + }, + "type" : "object", + "example" : { + "key" : "value" + } + } + } + } + }, + "errorTemplates" : { + "description" : "Current error templates at the time of export", + "type" : "array", + "items" : { + "description" : "Error templates for a service descriptor", + "type" : "object", + "required" : [ "serviceId", "template40x", "template50x", "templateBuild", "templateMaintenance", "messages" ], + "properties" : { + "serviceId" : { + "description" : "The Id of the service for which the error template is enabled", + "type" : "string", + "example" : "a string value" + }, + "template40x" : { + "description" : "The html template for 40x errors", + "type" : "string", + "example" : "a string value" + }, + "template50x" : { + "description" : "The html template for 50x errors", + "type" : "string", + "example" : "a string value" + }, + "templateBuild" : { + "description" : "The html template for build page", + "type" : "string", + "example" : "a string value" + }, + "templateMaintenance" : { + "description" : "The html template for maintenance page", + "type" : "string", + "example" : "a string value" + }, + "messages" : { + "description" : "Map for custom messages", + "additionalProperties" : { + "type" : "string" + }, + "type" : "object", + "example" : { + "key" : "value" + } + } + } + } + } + } + }, + "ImportExportStats" : { + "description" : "Global stats for the current Otoroshi instances", + "type" : "object", + "required" : [ "calls", "dataIn", "dataOut" ], + "properties" : { + "calls" : { + "format" : "int64", + "description" : "Number of calls to Otoroshi globally", + "type" : "integer", + "example" : 123 + }, + "dataIn" : { + "format" : "int64", + "description" : "The amount of data sent to Otoroshi globally", + "type" : "integer", + "example" : 123 + }, + "dataOut" : { + "format" : "int64", + "description" : "The amount of data sent from Otoroshi globally", + "type" : "integer", + "example" : 123 + } + } + }, + "IpFiltering" : { + "description" : "The filtering configuration block for a service of globally.", + "type" : "object", + "required" : [ "whitelist", "blacklist" ], + "properties" : { + "whitelist" : { + "description" : "Whitelisted IP addresses", + "type" : "array", + "items" : { + "type" : "string", + "format" : "ipv4", + "example" : "192.192.192.192" + } + }, + "blacklist" : { + "description" : "Blacklisted IP addresses", + "type" : "array", + "items" : { + "type" : "string", + "format" : "ipv4", + "example" : "192.192.192.192" + } + } + } + }, + "MailerSettings" : { + "description" : "Configuration for mailgun api client", + "type" : "object", + "required" : [ "apiKey", "domain" ], + "properties" : { + "type" : { + "description" : "Type of the mailer: console, generic, mailgun, mailjet", + "type" : "string", + "example" : "a string value" + }, + "eu" : { + "description" : "Mailgun mailer, use EU tenant api", + "type" : "boolean", + "example" : true + }, + "apiKey" : { + "description" : "Mailgun mailer api key", + "type" : "string", + "example" : "a string value" + }, + "domain" : { + "description" : "Mailgun mailer domain", + "type" : "string", + "example" : "a string value" + }, + "apiKeyPublic" : { + "description" : "Mailjet mailer public api key", + "type" : "string", + "example" : "a string value" + }, + "apiKeyPrivate" : { + "description" : "Mailjet mailer private api key", + "type" : "string", + "example" : "a string value" + }, + "url" : { + "description" : "Generic mailer url", + "type" : "string", + "example" : "a string value" + }, + "header" : { + "description" : "Generic mailer headers", + "additionalProperties" : { + "type" : "string" + }, + "type" : "object", + "example" : { + "key" : "value" + } + } + } + }, + "Patch" : { + "description" : "A set of changes described in JSON Patch format: http://jsonpatch.com/ (RFC 6902)", + "type" : "array", + "items" : { + "type" : "object", + "required" : [ "op", "path" ], + "properties" : { + "op" : { + "type" : "string", + "enum" : [ "add", "replace", "remove", "copy", "test" ] + }, + "path" : { + "type" : "string", + "example" : "a string value" + }, + "value" : { } + } + } + }, + "Quotas" : { + "description" : "Quotas state for an api key on a service group", + "type" : "object", + "required" : [ "authorizedCallsPerSec", "currentCallsPerSec", "remainingCallsPerSec", "authorizedCallsPerDay", "currentCallsPerDay", "remainingCallsPerDay", "authorizedCallsPerMonth", "currentCallsPerMonth", "remainingCallsPerMonth" ], + "properties" : { + "authorizedCallsPerSec" : { + "format" : "int64", + "description" : "The number of authorized calls per second", + "type" : "integer", + "example" : 123 + }, + "currentCallsPerSec" : { + "format" : "int64", + "description" : "The current number of calls per second", + "type" : "integer", + "example" : 123 + }, + "remainingCallsPerSec" : { + "format" : "int64", + "description" : "The remaining number of calls per second", + "type" : "integer", + "example" : 123 + }, + "authorizedCallsPerDay" : { + "format" : "int64", + "description" : "The number of authorized calls per day", + "type" : "integer", + "example" : 123 + }, + "currentCallsPerDay" : { + "format" : "int64", + "description" : "The current number of calls per day", + "type" : "integer", + "example" : 123 + }, + "remainingCallsPerDay" : { + "format" : "int64", + "description" : "The remaining number of calls per day", + "type" : "integer", + "example" : 123 + }, + "authorizedCallsPerMonth" : { + "format" : "int64", + "description" : "The number of authorized calls per month", + "type" : "integer", + "example" : 123 + }, + "currentCallsPerMonth" : { + "format" : "int64", + "description" : "The current number of calls per month", + "type" : "integer", + "example" : 123 + }, + "remainingCallsPerMonth" : { + "format" : "int64", + "description" : "The number of authorized calls per month", + "type" : "integer", + "example" : 123 + } + } + }, + "Service" : { + "description" : "An otoroshi service descriptor. Represent a forward HTTP call on a domain to another location with some optional api management mecanism", + "type" : "object", + "required" : [ "id", "groupId", "name", "env", "domain", "subdomain", "targets", "root", "enabled", "privateApp", "forceHttps", "maintenanceMode", "buildMode", "enforceSecureCommunication" ], + "properties" : { + "id" : { + "format" : "uuid", + "description" : "A unique random string to identify your service", + "type" : "string", + "example" : "110e8400-e29b-11d4-a716-446655440000" + }, + "groupId" : { + "description" : "Each service descriptor is attached to a group. A group can have one or more services. Each API key is linked to a group and allow access to every service in the group", + "type" : "string", + "example" : "a string value" + }, + "name" : { + "description" : "The name of your service. Only for debug and human readability purposes", + "type" : "string", + "example" : "a string value" + }, + "env" : { + "description" : "The line on which the service is available. Based on that value, the name of the line will be appended to the subdomain. For line prod, nothing will be appended. For example, if the subdomain is 'foo' and line is 'preprod', then the exposed service will be available at 'foo.preprod.mydomain'", + "type" : "string", + "example" : "a string value" + }, + "domain" : { + "description" : "The domain on which the service is available.", + "type" : "string", + "example" : "a string value" + }, + "subdomain" : { + "description" : "The subdomain on which the service is available", + "type" : "string", + "example" : "a string value" + }, + "targets" : { + "description" : "The list of target that Otoroshi will proxy and expose through the subdomain defined before. Otoroshi will do round-robin load balancing between all those targets with circuit breaker mecanism to avoid cascading failures", + "type" : "array", + "items" : { + "$ref" : "#/definitions/Target" + } + }, + "root" : { + "description" : "Otoroshi will append this root to any target choosen. If the specified root is '/api/foo', then a request to https://yyyyyyy/bar will actually hit https://xxxxxxxxx/api/foo/bar", + "type" : "string", + "example" : "a string value" + }, + "matchingRoot" : { + "description" : "The root path on which the service is available", + "type" : "string", + "example" : "a string value" + }, + "localHost" : { + "description" : "The host used localy, mainly localhost:xxxx", + "type" : "string", + "example" : "a string value" + }, + "localScheme" : { + "description" : "The scheme used localy, mainly http", + "type" : "string", + "example" : "a string value" + }, + "redirectToLocal" : { + "description" : "If you work locally with Otoroshi, you may want to use that feature to redirect one particuliar service to a local host. For example, you can relocate https://foo.preprod.bar.com to http://localhost:8080 to make some tests", + "type" : "boolean", + "example" : true + }, + "enabled" : { + "description" : "Activate or deactivate your service. Once disabled, users will get an error page saying the service does not exist", + "type" : "boolean", + "example" : true + }, + "userFacing" : { + "description" : "The fact that this service will be seen by users and cannot be impacted by the Snow Monkey", + "type" : "boolean", + "example" : true + }, + "privateApp" : { + "description" : "When enabled, user will be allowed to use the service (UI) only if they are registered users of the private apps domain", + "type" : "boolean", + "example" : true + }, + "forceHttps" : { + "description" : "Will force redirection to https:// if not present", + "type" : "boolean", + "example" : true + }, + "maintenanceMode" : { + "description" : "Display a maintainance page when a user try to use the service", + "type" : "boolean", + "example" : true + }, + "buildMode" : { + "description" : "Display a construction page when a user try to use the service", + "type" : "boolean", + "example" : true + }, + "enforceSecureCommunication" : { + "description" : "When enabled, Otoroshi will try to exchange headers with downstream service to ensure no one else can use the service from outside", + "type" : "boolean", + "example" : true + }, + "sendOtoroshiHeadersBack" : { + "description" : "When enabled, Otoroshi will send headers to consumer like request id, client latency, overhead, etc ...", + "type" : "boolean", + "example" : true + }, + "xForwardedHeaders" : { + "description" : "Send X-Forwarded-* headers", + "type" : "boolean", + "example" : true + }, + "overrideHost" : { + "description" : "Host header will be overriden with Host of the target", + "type" : "boolean", + "example" : true + }, + "secComExcludedPatterns" : { + "description" : "URI patterns excluded from secured communications", + "type" : "array", + "items" : { + "type" : "string", + "example" : "a string value" + } + }, + "publicPatterns" : { + "description" : "By default, every services are private only and you'll need an API key to access it. However, if you want to expose a public UI, you can define one or more public patterns (regex) to allow access to anybody. For example if you want to allow anybody on any URL, just use '/.*'", + "type" : "array", + "items" : { + "type" : "string", + "example" : "a string value" + } + }, + "privatePatterns" : { + "description" : "If you define a public pattern that is a little bit too much, you can make some of public URL private again", + "type" : "array", + "items" : { + "type" : "string", + "example" : "a string value" + } + }, + "ipFiltering" : { + "$ref" : "#/definitions/IpFiltering" + }, + "api" : { + "$ref" : "#/definitions/ExposedApi" + }, + "healthCheck" : { + "$ref" : "#/definitions/HealthCheck" + }, + "clientConfig" : { + "$ref" : "#/definitions/ClientConfig" + }, + "Canary" : { + "$ref" : "#/definitions/Canary" + }, + "statsdConfig" : { + "$ref" : "#/definitions/StatsdConfig" + }, + "chaosConfig" : { + "$ref" : "#/definitions/ChaosConfig" + }, + "jwtVerifier" : { + "oneOf" : [ { + "$ref" : "#/definitions/LocalJwtVerifier" + }, { + "$ref" : "#/definitions/RefJwtVerifier" + } ] + }, + "secComSettings" : { + "oneOf" : [ { + "$ref" : "#/definitions/HSAlgoSettings" + }, { + "$ref" : "#/definitions/RSAlgoSettings" + }, { + "$ref" : "#/definitions/ESAlgoSettings" + }, { + "$ref" : "#/definitions/JWKSAlgoSettings" + } ] + }, + "metadata" : { + "description" : "Just a bunch of random properties", + "additionalProperties" : { + "type" : "string" + }, + "type" : "object", + "example" : { + "key" : "value" + } + }, + "matchingHeaders" : { + "description" : "Specify headers that MUST be present on client request to route it. Useful to implement versioning", + "additionalProperties" : { + "type" : "string" + }, + "type" : "object", + "example" : { + "key" : "value" + } + }, + "additionalHeaders" : { + "description" : "Specify headers that will be added to each client request. Useful to add authentication", + "additionalProperties" : { + "type" : "string" + }, + "type" : "object", + "example" : { + "key" : "value" + } + }, + "authConfigRef" : { + "description" : "A reference to a global auth module config", + "type" : "string", + "example" : "a string value" + }, + "transformerRef" : { + "description" : "A reference to a request transformer", + "type" : "string", + "example" : "a string value" + }, + "clientValidatorRef" : { + "description" : "A reference to validation authority", + "type" : "string", + "example" : "a string value" + }, + "cors" : { + "$ref" : "#/definitions/CorsSettings" + }, + "redirection" : { + "$ref" : "#/definitions/RedirectionSettings" + }, + "gzip" : { + "$ref" : "#/definitions/Gzip" + }, + "headersVerification" : { + "description" : "Specify headers that will be verified after routing.", + "additionalProperties" : { + "type" : "string" + }, + "type" : "object", + "example" : { + "key" : "value" + } + } + } + }, + "SimpleAdmin" : { + "description" : "Administrator using just login/password tuple to access Otoroshi", + "type" : "object", + "required" : [ "username", "label", "password", "createdAt" ], + "properties" : { + "username" : { + "description" : "The email address of the user", + "type" : "string", + "example" : "a string value" + }, + "label" : { + "description" : "The label for the user", + "type" : "string", + "example" : "a string value" + }, + "password" : { + "description" : "The hashed password of the user", + "type" : "string", + "example" : "a string value" + }, + "createdAt" : { + "format" : "int64", + "description" : "The creation date of the user", + "type" : "integer", + "example" : 123 + } + } + }, + "Stats" : { + "description" : "Live stats for a service or globally", + "type" : "object", + "required" : [ "calls", "dataIn", "dataOut", "rate", "duration", "overhead", "dataInRate", "dataOutRate", "concurrentHandledRequests" ], + "properties" : { + "calls" : { + "format" : "int64", + "description" : "Number of calls on the specified service or globally", + "type" : "integer", + "example" : 123 + }, + "dataIn" : { + "format" : "int64", + "description" : "The amount of data sent to the specified service or Otoroshi globally", + "type" : "integer", + "example" : 123 + }, + "dataOut" : { + "format" : "int64", + "description" : "The amount of data sent from the specified service or Otoroshi globally", + "type" : "integer", + "example" : 123 + }, + "rate" : { + "format" : "double", + "description" : "The rate of data sent from and to the specified service or Otoroshi globally", + "type" : "integer", + "example" : 42.2 + }, + "duration" : { + "format" : "double", + "description" : "The average duration for a call", + "type" : "integer", + "example" : 42.2 + }, + "overhead" : { + "format" : "double", + "description" : "The average overhead time induced by Otoroshi for each call", + "type" : "integer", + "example" : 42.2 + }, + "dataInRate" : { + "format" : "double", + "description" : "The rate of data sent to the specified service or Otoroshi globally", + "type" : "integer", + "example" : 42.2 + }, + "dataOutRate" : { + "format" : "double", + "description" : "The rate of data sent from the specified service or Otoroshi globally", + "type" : "integer", + "example" : 42.2 + }, + "concurrentHandledRequests" : { + "format" : "int64", + "description" : "The number of concurrent request currently", + "type" : "integer", + "example" : 123 + } + } + }, + "StatsdConfig" : { + "description" : "The configuration for statsd metrics push", + "type" : "object", + "required" : [ "host", "port", "datadog" ], + "properties" : { + "host" : { + "description" : "The host of the StatsD agent", + "type" : "string", + "example" : "a string value" + }, + "port" : { + "format" : "int32", + "description" : "The port of the StatsD agent", + "type" : "integer", + "example" : 123123 + }, + "datadog" : { + "description" : "Datadog agent", + "type" : "boolean", + "example" : true + } + } + }, + "Target" : { + "description" : "A Target is where an HTTP call will be forwarded in the end from a service domain", + "type" : "object", + "required" : [ "host", "scheme" ], + "properties" : { + "host" : { + "format" : "hostname", + "description" : "The host on which the HTTP call will be forwarded. Can be a domain name, or an IP address. Can also have a port", + "type" : "string", + "example" : "www.google.com" + }, + "scheme" : { + "description" : "The protocol used for communication. Can be http or https", + "type" : "string", + "example" : "a string value" + } + } + }, + "U2FAdmin" : { + "description" : "Administrator using FIDO U2F device to access Otoroshi", + "type" : "object", + "required" : [ "username", "label", "password", "createdAt", "registration" ], + "properties" : { + "username" : { + "description" : "The email address of the user", + "type" : "string", + "example" : "a string value" + }, + "label" : { + "description" : "The label for the user", + "type" : "string", + "example" : "a string value" + }, + "password" : { + "description" : "The hashed password of the user", + "type" : "string", + "example" : "a string value" + }, + "createdAt" : { + "format" : "int64", + "description" : "The creation date of the user", + "type" : "integer", + "example" : 123 + }, + "registration" : { + "description" : "The U2F registration slug", + "additionalProperties" : { + "type" : "string" + }, + "type" : "object", + "example" : { + "key" : "value" + } + } + } + }, + "Webhook" : { + "description" : "A callback URL where events are posted", + "type" : "object", + "required" : [ "url", "headers" ], + "properties" : { + "url" : { + "format" : "uri", + "description" : "The URL where events are posted", + "type" : "string", + "example" : "http://www.google.com" + }, + "headers" : { + "description" : "Headers to authorize the call or whatever", + "additionalProperties" : { + "type" : "string" + }, + "type" : "object", + "example" : { + "key" : "value" + } + } + } + }, + "BadResponse" : { + "description" : "An HTTP response that is not supposed to be returned by a service", + "type" : "object", + "required" : [ "status", "body", "headers" ], + "properties" : { + "status" : { + "format" : "int32", + "description" : "The HTTP status for the response", + "type" : "integer", + "example" : 123123 + }, + "body" : { + "description" : "The body of the HTTP response", + "type" : "string", + "example" : "a string value" + }, + "headers" : { + "description" : "The HTTP headers of the response", + "additionalProperties" : { + "type" : "string" + }, + "type" : "object", + "example" : { + "key" : "value" + } + } + } + }, + "LargeRequestFaultConfig" : { + "description" : "Config for large request injection fault", + "type" : "object", + "required" : [ "ratio", "additionalRequestSize" ], + "properties" : { + "ratio" : { + "format" : "double", + "description" : "The percentage of requests affected by this fault. Value should be between 0.0 and 1.0", + "type" : "integer", + "example" : 42.2 + }, + "additionalRequestSize" : { + "format" : "int32", + "description" : "The size added to the request body in bytes. Added payload will be spaces only.", + "type" : "integer", + "example" : 123123 + } + } + }, + "LargeResponseFaultConfig" : { + "description" : "Config for large response injection fault", + "type" : "object", + "required" : [ "ratio", "additionalResponseSize" ], + "properties" : { + "ratio" : { + "format" : "double", + "description" : "The percentage of requests affected by this fault. Value should be between 0.0 and 1.0", + "type" : "integer", + "example" : 42.2 + }, + "additionalRequestSize" : { + "format" : "int32", + "description" : "The size added to the response body in bytes. Added payload will be spaces only.", + "type" : "integer", + "example" : 123123 + } + } + }, + "LatencyInjectionFaultConfig" : { + "description" : "Config for large latency injection fault", + "type" : "object", + "required" : [ "ratio", "from", "to" ], + "properties" : { + "ratio" : { + "format" : "double", + "description" : "The percentage of requests affected by this fault. Value should be between 0.0 and 1.0", + "type" : "integer", + "example" : 42.2 + }, + "from" : { + "format" : "int32", + "description" : "The start range of latency added to the request", + "type" : "integer", + "example" : 123123 + }, + "to" : { + "format" : "int32", + "description" : "The end range of latency added to the request", + "type" : "integer", + "example" : 123123 + } + } + }, + "BadResponsesFaultConfig" : { + "description" : "Config for bad requests injection fault", + "type" : "object", + "required" : [ "ratio", "responses" ], + "properties" : { + "ratio" : { + "format" : "double", + "description" : "The percentage of requests affected by this fault. Value should be between 0.0 and 1.0", + "type" : "integer", + "example" : 42.2 + }, + "responses" : { + "description" : "The possibles responses", + "type" : "array", + "items" : { + "$ref" : "#/definitions/BadResponse" + } + } + } + }, + "ChaosConfig" : { + "description" : "Configuration for the faults that can be injected in requests", + "type" : "object", + "required" : [ "enabled" ], + "properties" : { + "enabled" : { + "description" : "Whether or not this config is enabled", + "type" : "boolean", + "example" : true + }, + "largeRequestFaultConfig" : { + "$ref" : "#/definitions/LargeRequestFaultConfig" + }, + "largeResponseFaultConfig" : { + "$ref" : "#/definitions/LargeResponseFaultConfig" + }, + "latencyInjectionFaultConfig" : { + "$ref" : "#/definitions/LatencyInjectionFaultConfig" + }, + "badResponsesFaultConfig" : { + "$ref" : "#/definitions/BadResponsesFaultConfig" + } + } + }, + "OutageStrategy" : { + "type" : "string", + "enum" : [ "OneServicePerGroup", "AllServicesPerGroup" ] + }, + "SnowMonkeyConfig" : { + "description" : "Configuration for the faults that can be injected in requests. The name Snow Monkey is an hommage to Netflix's Chaos Monkey 😉", + "type" : "object", + "required" : [ "enabled", "outageStrategy", "includeUserFacingDescriptors", "dryRun", "timesPerDay", "startTime", "stopTime", "outageDurationFrom", "outageDurationTo", "targetGroups", "chaosConfig" ], + "properties" : { + "enabled" : { + "description" : "Whether or not this config is enabled", + "type" : "boolean", + "example" : true + }, + "outageStrategy" : { + "description" : "", + "$ref" : "#/definitions/OutageStrategy" + }, + "includeUserFacingDescriptors" : { + "description" : "Whether or not user facing apps. will be impacted by Snow Monkey", + "type" : "boolean", + "example" : true + }, + "dryRun" : { + "description" : "Whether or not outages will actualy impact requests", + "type" : "boolean", + "example" : true + }, + "timesPerDay" : { + "format" : "int32", + "description" : "Number of time per day each service will be outage", + "type" : "integer", + "example" : 123123 + }, + "startTime" : { + "format" : "time", + "description" : "Start time of Snow Monkey each day", + "type" : "string", + "example" : "17:32:28.000" + }, + "stopTime" : { + "format" : "time", + "description" : "Stop time of Snow Monkey each day", + "type" : "string", + "example" : "17:32:28.000" + }, + "outageDurationFrom" : { + "format" : "int32", + "description" : "Start of outage duration range", + "type" : "integer", + "example" : 123123 + }, + "outageDurationTo" : { + "format" : "int32", + "description" : "End of outage duration range", + "type" : "integer", + "example" : 123123 + }, + "targetGroups" : { + "description" : "Groups impacted by Snow Monkey. If empty, all groups will be impacted", + "type" : "array", + "items" : { + "type" : "string", + "example" : "a string value" + } + }, + "chaosConfig" : { + "$ref" : "#/definitions/ChaosConfig" + } + } + }, + "Outage" : { + "description" : "An outage by the Snow Monkey on a service", + "type" : "object", + "required" : [ "descriptorId", "descriptorName", "until", "duration" ], + "properties" : { + "descriptorId" : { + "description" : "The service impacted by outage", + "type" : "string", + "example" : "a string value" + }, + "descriptorName" : { + "description" : "The name of service impacted by outage", + "type" : "string", + "example" : "a string value" + }, + "until" : { + "format" : "time", + "description" : "The end of the outage", + "type" : "string", + "example" : "17:32:28.000" + }, + "duration" : { + "format" : "int32", + "description" : "The duration of the outage", + "type" : "integer", + "example" : 123123 + } + } + }, + "RefJwtVerifier" : { + "description" : "Reference to a global JWT verifier", + "type" : "object", + "required" : [ "type", "id", "enabled" ], + "properties" : { + "type" : { + "description" : "A string with value 'ref'", + "type" : "string", + "example" : "a string value" + }, + "id" : { + "description" : "The id of the GlobalJWTVerifier", + "type" : "string", + "example" : "a string value" + }, + "enabled" : { + "description" : "Is it enabled", + "type" : "boolean", + "example" : true + } + } + }, + "LocalJwtVerifier" : { + "description" : "A JWT verifier used only for the current service descriptor", + "type" : "object", + "required" : [ "type", "enabled", "strict", "source", "algoSettings", "strategy" ], + "properties" : { + "type" : { + "description" : "A string with value 'local'", + "type" : "string", + "example" : "a string value" + }, + "enabled" : { + "description" : "Is it enabled", + "type" : "boolean", + "example" : true + }, + "strict" : { + "description" : "Does it fail if JWT not found", + "type" : "boolean", + "example" : true + }, + "source" : { + "oneOf" : [ { + "$ref" : "#/definitions/InQueryParam" + }, { + "$ref" : "#/definitions/InHeader" + }, { + "$ref" : "#/definitions/InCookie" + } ] + }, + "algoSettings" : { + "oneOf" : [ { + "$ref" : "#/definitions/HSAlgoSettings" + }, { + "$ref" : "#/definitions/RSAlgoSettings" + }, { + "$ref" : "#/definitions/ESAlgoSettings" + }, { + "$ref" : "#/definitions/JWKSAlgoSettings" + } ] + }, + "strategy" : { + "oneOf" : [ { + "$ref" : "#/definitions/PassThrough" + }, { + "$ref" : "#/definitions/Sign" + }, { + "$ref" : "#/definitions/Transform" + } ] + } + } + }, + "InQueryParam" : { + "description" : "JWT location in a query param", + "type" : "object", + "required" : [ "type", "name" ], + "properties" : { + "type" : { + "description" : "String with value InQueryParam", + "type" : "string", + "example" : "a string value" + }, + "name" : { + "description" : "Name of the query param", + "type" : "string", + "example" : "a string value" + } + } + }, + "InHeader" : { + "description" : "JWT location in a header", + "type" : "object", + "required" : [ "type", "name", "remove" ], + "properties" : { + "type" : { + "description" : "String with value InHeader", + "type" : "string", + "example" : "a string value" + }, + "name" : { + "description" : "Name of the header", + "type" : "string", + "example" : "a string value" + }, + "remove" : { + "description" : "Remove regex inside the value, like 'Bearer '", + "type" : "string", + "example" : "a string value" + } + } + }, + "InCookie" : { + "description" : "JWT location in a cookie", + "type" : "object", + "required" : [ "type", "name" ], + "properties" : { + "type" : { + "description" : "String with value InCookie", + "type" : "string", + "example" : "a string value" + }, + "name" : { + "description" : "Name of the cookie", + "type" : "string", + "example" : "a string value" + } + } + }, + "HSAlgoSettings" : { + "description" : "Settings for an HMAC + SHA signing algorithm", + "type" : "object", + "required" : [ "type", "size", "secret" ], + "properties" : { + "type" : { + "description" : "String with value HSAlgoSettings", + "type" : "string", + "example" : "a string value" + }, + "size" : { + "format" : "int32", + "description" : "Size for SHA function. can be 256, 384 or 512", + "type" : "integer", + "example" : 123123 + }, + "secret" : { + "description" : "The secret value for the HMAC function", + "type" : "string", + "example" : "a string value" + } + } + }, + "RSAlgoSettings" : { + "description" : "Settings for an HMAC + SHA signing algorithm", + "type" : "object", + "required" : [ "type", "size", "publicKey" ], + "properties" : { + "type" : { + "description" : "String with value RSAlgoSettings", + "type" : "string", + "example" : "a string value" + }, + "size" : { + "format" : "int32", + "description" : "Size for SHA function. can be 256, 384 or 512", + "type" : "integer", + "example" : 123123 + }, + "publicKey" : { + "description" : "The public key for the RSA function", + "type" : "string", + "example" : "a string value" + }, + "privateKey" : { + "description" : "The private key for the RSA function", + "type" : "string", + "example" : "a string value" + } + } + }, + "ESAlgoSettings" : { + "description" : "Settings for an EC + SHA signing algorithm", + "type" : "object", + "required" : [ "type", "size", "publicKey" ], + "properties" : { + "type" : { + "description" : "String with value ESAlgoSettings", + "type" : "string", + "example" : "a string value" + }, + "size" : { + "format" : "int32", + "description" : "Size for SHA function. can be 256, 384 or 512", + "type" : "integer", + "example" : 123123 + }, + "publicKey" : { + "description" : "The public key for the RSA function", + "type" : "string", + "example" : "a string value" + }, + "privateKey" : { + "description" : "The private key for the RSA function", + "type" : "string", + "example" : "a string value" + } + } + }, + "JWKSAlgoSettings" : { + "description" : "Settings for a JWK set", + "type" : "object", + "required" : [ "type", "size", "publicKey" ], + "properties" : { + "type" : { + "description" : "String with value JWKSAlgoSettings", + "type" : "string", + "example" : "a string value" + }, + "url" : { + "description" : "The url for the http call", + "type" : "string", + "example" : "a string value" + }, + "headers" : { + "description" : "The headers for the http call", + "additionalProperties" : { + "type" : "string" + }, + "type" : "object", + "example" : { + "key" : "value" + } + }, + "timeout" : { + "format" : "int64", + "description" : "The timeout of the http call", + "type" : "integer", + "example" : 123 + }, + "ttl" : { + "format" : "int64", + "description" : "The ttl of the keyset", + "type" : "integer", + "example" : 123 + }, + "kty" : { + "description" : "The type of key: RSA or EC", + "type" : "string", + "example" : "a string value" + } + } + }, + "MappingSettings" : { + "description" : "Settings to change fields of a JWT token", + "type" : "object", + "required" : [ "map", "values", "remove" ], + "properties" : { + "map" : { + "description" : "Fields to rename", + "additionalProperties" : { + "type" : "string" + }, + "type" : "object", + "example" : { + "key" : "value" + } + }, + "values" : { + "description" : "Fields to set", + "additionalProperties" : { + "type" : "string" + }, + "type" : "object", + "example" : { + "key" : "value" + } + }, + "remove" : { + "description" : "Fields to remove", + "type" : "array", + "items" : { + "type" : "string", + "example" : "a string value" + } + } + } + }, + "TransformSettings" : { + "description" : "Settings to transform a JWT token and its location", + "type" : "object", + "required" : [ "location", "mappingSettings" ], + "properties" : { + "location" : { + "oneOf" : [ { + "$ref" : "#/definitions/InQueryParam" + }, { + "$ref" : "#/definitions/InHeader" + }, { + "$ref" : "#/definitions/InCookie" + } ] + }, + "mappingSettings" : { + "$ref" : "#/definitions/MappingSettings" + } + } + }, + "VerificationSettings" : { + "description" : "Settings to verify the value of JWT token fields", + "type" : "object", + "required" : [ "fields" ], + "properties" : { + "fields" : { + "description" : "Fields to verify with their values", + "additionalProperties" : { + "type" : "string" + }, + "type" : "object", + "example" : { + "key" : "value" + } + }, + "mappingSettings" : { + "$ref" : "#/definitions/MappingSettings" + } + } + }, + "PassThrough" : { + "description" : "Strategy where only signature and field values are verified", + "type" : "object", + "required" : [ "type", "verificationSettings" ], + "properties" : { + "type" : { + "description" : "String with value PassThrough", + "type" : "string", + "example" : "a string value" + }, + "verificationSettings" : { + "$ref" : "#/definitions/VerificationSettings" + } + } + }, + "Sign" : { + "description" : "Strategy where signature and field values are verified, and then token si re-signed", + "type" : "object", + "required" : [ "type", "verificationSettings", "algoSettings" ], + "properties" : { + "type" : { + "description" : "String with value Sign", + "type" : "string", + "example" : "a string value" + }, + "verificationSettings" : { + "$ref" : "#/definitions/VerificationSettings" + }, + "algoSettings" : { + "oneOf" : [ { + "$ref" : "#/definitions/HSAlgoSettings" + }, { + "$ref" : "#/definitions/RSAlgoSettings" + }, { + "$ref" : "#/definitions/ESAlgoSettings" + }, { + "$ref" : "#/definitions/JWKSAlgoSettings" + } ] + } + } + }, + "Transform" : { + "description" : "Strategy where signature and field values are verified, trasnformed and then token si re-signed", + "type" : "object", + "required" : [ "type", "verificationSettings", "algoSettings" ], + "properties" : { + "type" : { + "description" : "String with value Transform", + "type" : "string", + "example" : "a string value" + }, + "verificationSettings" : { + "$ref" : "#/definitions/VerificationSettings" + }, + "transformSettings" : { + "$ref" : "#/definitions/TransformSettings" + }, + "algoSettings" : { + "oneOf" : [ { + "$ref" : "#/definitions/HSAlgoSettings" + }, { + "$ref" : "#/definitions/RSAlgoSettings" + }, { + "$ref" : "#/definitions/ESAlgoSettings" + }, { + "$ref" : "#/definitions/JWKSAlgoSettings" + } ] + } + } + }, + "GlobalJwtVerifier" : { + "description" : "A JWT verifier used by multiple service descriptor", + "type" : "object", + "required" : [ "type", "id", "name", "desc", "enabled", "strict", "source", "algoSettings", "strategy" ], + "properties" : { + "id" : { + "description" : "Verifier id", + "type" : "string", + "example" : "a string value" + }, + "name" : { + "description" : "Verifier name", + "type" : "string", + "example" : "a string value" + }, + "desc" : { + "description" : "Verifier description", + "type" : "string", + "example" : "a string value" + }, + "enabled" : { + "description" : "Is it enabled", + "type" : "boolean", + "example" : true + }, + "strict" : { + "description" : "Does it fail if JWT not found", + "type" : "boolean", + "example" : true + }, + "source" : { + "oneOf" : [ { + "$ref" : "#/definitions/InQueryParam" + }, { + "$ref" : "#/definitions/InHeader" + }, { + "$ref" : "#/definitions/InCookie" + } ] + }, + "algoSettings" : { + "oneOf" : [ { + "$ref" : "#/definitions/HSAlgoSettings" + }, { + "$ref" : "#/definitions/RSAlgoSettings" + }, { + "$ref" : "#/definitions/ESAlgoSettings" + }, { + "$ref" : "#/definitions/JWKSAlgoSettings" + } ] + }, + "strategy" : { + "oneOf" : [ { + "$ref" : "#/definitions/PassThrough" + }, { + "$ref" : "#/definitions/Sign" + }, { + "$ref" : "#/definitions/Transform" + } ] + } + } + }, + "GenericOauth2ModuleConfig" : { + "description" : "Settings to authenticate users using a generic OAuth2 provider", + "type" : "object", + "required" : [ "type", "id", "name", "desc", "sessionMaxAge", "clientId", "clientSecret", "authorizeUrl", "tokenUrl", "userInfoUrl", "loginUrl", "logoutUrl", "callbackUrl", "accessTokenField", "nameField", "emailField", "otoroshiDataField" ], + "properties" : { + "type" : { + "description" : "Type of settings. value is oauth2", + "type" : "string", + "example" : "a string value" + }, + "id" : { + "description" : "Unique id of the config", + "type" : "string", + "example" : "a string value" + }, + "name" : { + "description" : "Name of the config", + "type" : "string", + "example" : "a string value" + }, + "desc" : { + "description" : "Description of the config", + "type" : "string", + "example" : "a string value" + }, + "sessionMaxAge" : { + "format" : "int32", + "description" : "Max age of the session", + "type" : "integer", + "example" : 123123 + }, + "clientId" : { + "description" : "OAuth Client id", + "type" : "string", + "example" : "a string value" + }, + "clientSecret" : { + "description" : "OAuth Client secret", + "type" : "string", + "example" : "a string value" + }, + "authorizeUrl" : { + "description" : "OAuth authorize URL", + "type" : "string", + "example" : "a string value" + }, + "tokenUrl" : { + "description" : "OAuth token URL", + "type" : "string", + "example" : "a string value" + }, + "userInfoUrl" : { + "description" : "OAuth userinfo to get user profile", + "type" : "string", + "example" : "a string value" + }, + "loginUrl" : { + "description" : "OAuth login URL", + "type" : "string", + "example" : "a string value" + }, + "logoutUrl" : { + "description" : "OAuth logout URL", + "type" : "string", + "example" : "a string value" + }, + "callbackUrl" : { + "description" : "Otoroshi callback URL", + "type" : "string", + "example" : "a string value" + }, + "scope" : { + "description" : "The scope of the token", + "type" : "string", + "example" : "a string value" + }, + "claims" : { + "description" : "The claims of the token", + "type" : "string", + "example" : "a string value" + }, + "accessTokenField" : { + "description" : "Field name to get access token", + "type" : "string", + "example" : "a string value" + }, + "nameField" : { + "description" : "Field name to get name from user profile", + "type" : "string", + "example" : "a string value" + }, + "emailField" : { + "description" : "Field name to get email from user profile", + "type" : "string", + "example" : "a string value" + }, + "otoroshiDataField" : { + "description" : "Field name to get otoroshi metadata from. You can specify sub fields using | as separator", + "type" : "string", + "example" : "a string value" + }, + "oidConfig" : { + "description" : "URL of the OIDC config. file", + "type" : "string", + "example" : "a string value" + }, + "useJson" : { + "description" : "Use JSON or URL Form Encoded as payload with the OAuth provider", + "type" : "boolean", + "example" : true + }, + "useCookies" : { + "description" : "Use for redirection to actual service", + "type" : "boolean", + "example" : true + }, + "readProfileFromToken" : { + "description" : "The user profile will be read from the JWT token in id_token", + "type" : "boolean", + "example" : true + }, + "jwtVerifier" : { + "oneOf" : [ { + "$ref" : "#/definitions/HSAlgoSettings" + }, { + "$ref" : "#/definitions/RSAlgoSettings" + }, { + "$ref" : "#/definitions/ESAlgoSettings" + }, { + "$ref" : "#/definitions/JWKSAlgoSettings" + } ], + "description" : "Algo. settings to verify JWT token" + } + } + }, + "InMemoryAuthModuleConfig" : { + "description" : "Settings to authenticate users using the in memory user store", + "type" : "object", + "required" : [ "type", "id", "name", "desc", "users", "sessionMaxAge" ], + "properties" : { + "type" : { + "description" : "Type of settings. value is basic", + "type" : "string", + "example" : "a string value" + }, + "id" : { + "description" : "Unique id of the config", + "type" : "string", + "example" : "a string value" + }, + "name" : { + "description" : "Name of the config", + "type" : "string", + "example" : "a string value" + }, + "desc" : { + "description" : "Description of the config", + "type" : "string", + "example" : "a string value" + }, + "sessionMaxAge" : { + "description" : "Max age of the session", + "type" : "string", + "example" : "a string value" + }, + "users" : { + "description" : "List of users", + "type" : "array", + "items" : { + "$ref" : "#/definitions/InMemoryUser" + } + } + } + }, + "LdapAuthModuleConfig" : { + "description" : "Settings to authenticate users using a generic OAuth2 provider", + "type" : "object", + "required" : [ "type", "id", "name", "desc", "sessionMaxAge", "serverUrl", "searchBase", "userBase", "groupFilter", "searchFilter", "adminUsername", "adminPassword", "nameField", "emailField", "metadataField" ], + "properties" : { + "type" : { + "description" : "Type of settings. value is ldap", + "type" : "string", + "example" : "a string value" + }, + "id" : { + "description" : "Unique id of the config", + "type" : "string", + "example" : "a string value" + }, + "name" : { + "description" : "Name of the config", + "type" : "string", + "example" : "a string value" + }, + "desc" : { + "description" : "Description of the config", + "type" : "string", + "example" : "a string value" + }, + "sessionMaxAge" : { + "format" : "int32", + "description" : "Max age of the session", + "type" : "integer", + "example" : 123123 + }, + "serverUrl" : { + "description" : "URL of the ldap server", + "type" : "string", + "example" : "a string value" + }, + "searchBase" : { + "description" : "LDAP search base", + "type" : "string", + "example" : "a string value" + }, + "userBase" : { + "description" : "LDAP user base DN", + "type" : "string", + "example" : "a string value" + }, + "groupFilter" : { + "description" : "Filter for groups", + "type" : "string", + "example" : "a string value" + }, + "searchFilter" : { + "description" : "Filter for users", + "type" : "string", + "example" : "a string value" + }, + "adminUsername" : { + "description" : "The admin username", + "type" : "string", + "example" : "a string value" + }, + "adminPassword" : { + "description" : "The admin password", + "type" : "string", + "example" : "a string value" + }, + "nameField" : { + "description" : "Field name to get name from user profile", + "type" : "string", + "example" : "a string value" + }, + "emailField" : { + "description" : "Field name to get email from user profile", + "type" : "string", + "example" : "a string value" + }, + "otoroshiDataField" : { + "description" : "Field name to get otoroshi metadata from. You can specify sub fields using | as separator", + "type" : "string", + "example" : "a string value" + } + } + }, + "CorsSettings" : { + "description" : "The configuration for cors support", + "type" : "object", + "required" : [ "enabled", "allowOrigin", "exposeHeaders", "allowHeaders", "allowMethods", "excludedPatterns", "maxAge", "allowCredentials" ], + "properties" : { + "enabled" : { + "description" : "Whether or not cors is enabled", + "type" : "boolean", + "example" : true + }, + "allowOrigin" : { + "description" : "The cors allowed origin", + "type" : "string", + "example" : "a string value" + }, + "exposeHeaders" : { + "description" : "The cors exposed header", + "type" : "array", + "items" : { + "type" : "string", + "example" : "a string value" + } + }, + "allowHeaders" : { + "description" : "The cors allowed headers", + "type" : "array", + "items" : { + "type" : "string", + "example" : "a string value" + } + }, + "allowMethods" : { + "description" : "The cors allowed methods", + "type" : "array", + "items" : { + "type" : "string", + "example" : "a string value" + } + }, + "excludedPatterns" : { + "description" : "The cors excluded patterns", + "type" : "array", + "items" : { + "type" : "string", + "example" : "a string value" + } + }, + "maxAge" : { + "format" : "int32", + "description" : "Cors max age", + "type" : "integer", + "example" : 123123 + }, + "allowCredentials" : { + "description" : "Allow to pass credentials", + "type" : "boolean", + "example" : true + } + } + }, + "RedirectionSettings" : { + "description" : "The configuration for redirection per service", + "type" : "object", + "required" : [ "enabled", "to", "code" ], + "properties" : { + "enabled" : { + "description" : "Whether or not redirection is enabled", + "type" : "boolean", + "example" : true + }, + "to" : { + "description" : "The location for redirection", + "type" : "string", + "example" : "a string value" + }, + "code" : { + "format" : "int32", + "description" : "The http redirect code", + "type" : "integer", + "example" : 123123 + } + } + }, + "InMemoryUser" : { + "description" : "A user", + "type" : "object", + "required" : [ "name", "password", "email", "metadata" ], + "properties" : { + "name" : { + "description" : "Name of the user", + "type" : "string", + "example" : "a string value" + }, + "email" : { + "description" : "Email of the user", + "type" : "string", + "example" : "a string value" + }, + "password" : { + "description" : "Password of the user (BCrypt hash)", + "type" : "string", + "example" : "a string value" + }, + "metadata" : { + "description" : "Metadata of the user", + "additionalProperties" : { + "type" : "string" + }, + "type" : "object", + "example" : { + "key" : "value" + } + } + } + }, + "LdapUser" : { + "description" : "A user", + "type" : "object", + "required" : [ "name", "email", "metadata" ], + "properties" : { + "name" : { + "description" : "Name of the user", + "type" : "string", + "example" : "a string value" + }, + "email" : { + "description" : "Email of the user", + "type" : "string", + "example" : "a string value" + }, + "metadata" : { + "description" : "Metadata of the user", + "additionalProperties" : { + "type" : "string" + }, + "type" : "object", + "example" : { + "key" : "value" + } + } + } + }, + "Gzip" : { + "description" : "Configuration for gzip of service responses", + "type" : "object", + "required" : [ "enabled", "excludedPatterns", "whiteList", "blackList", "bufferSize", "chunkedThreshold", "compressionLevel" ], + "properties" : { + "enabled" : { + "description" : "Whether gzip compression is enabled or not", + "type" : "boolean", + "example" : true + }, + "excludedPatterns" : { + "description" : "Patterns that are excluded from gzipping", + "type" : "array", + "items" : { + "type" : "string", + "example" : "a string value" + } + }, + "whiteList" : { + "description" : "Whitelisted mime types. Wildcard supported", + "type" : "array", + "items" : { + "type" : "string", + "example" : "a string value" + } + }, + "blackList" : { + "description" : "Blacklisted mime types. Wildcard supported", + "type" : "array", + "items" : { + "type" : "string", + "example" : "a string value" + } + }, + "bufferSize" : { + "format" : "int64", + "description" : "Size of the GZip buffer", + "type" : "integer", + "example" : 123 + }, + "chunkedThreshold" : { + "format" : "int64", + "description" : "Threshold for chunking data", + "type" : "integer", + "example" : 123 + }, + "compressionLevel" : { + "format" : "int32", + "description" : "Compression level. From 0 to 9", + "type" : "integer", + "example" : 123123 + } + } + }, + "Script" : { + "description" : "A script to transformer otoroshi requests ", + "type" : "object", + "required" : [ "id", "name", "desc", "code" ], + "properties" : { + "id" : { + "description" : "The id of the script", + "type" : "string", + "example" : "a string value" + }, + "name" : { + "description" : "The name of the script", + "type" : "string", + "example" : "a string value" + }, + "desc" : { + "description" : "The description of the script", + "additionalProperties" : { + "type" : "string" + }, + "type" : "object", + "example" : { + "key" : "value" + } + }, + "code" : { + "description" : "The code of the script", + "additionalProperties" : { + "type" : "string" + }, + "type" : "object", + "example" : { + "key" : "value" + } + } + } + }, + "ScriptCompilationResult" : { + "description" : "The result of the compilation of a Script", + "type" : "object", + "required" : [ "done" ], + "properties" : { + "done" : { + "description" : "Is the task done or not", + "type" : "boolean", + "example" : true + }, + "error" : { + "$ref" : "#/definitions/ScriptCompilationError" + } + } + }, + "ScriptCompilationError" : { + "description" : "The error of the compilation of a Script", + "type" : "object", + "required" : [ "line", "column", "file", "rawMessage", "message" ], + "properties" : { + "line" : { + "description" : "The line of the error", + "type" : "string", + "example" : "a string value" + }, + "column" : { + "description" : "The column of the error", + "type" : "string", + "example" : "a string value" + }, + "file" : { + "description" : "The file where the error is located", + "additionalProperties" : { + "type" : "string" + }, + "type" : "object", + "example" : { + "key" : "value" + } + }, + "rawMessage" : { + "description" : "The raw message from the compiler", + "additionalProperties" : { + "type" : "string" + }, + "type" : "object", + "example" : { + "key" : "value" + } + }, + "message" : { + "description" : "The message to display for the error", + "additionalProperties" : { + "type" : "string" + }, + "type" : "object", + "example" : { + "key" : "value" + } + } + } + }, + "Certificate" : { + "description" : "A SSL/TLS X509 certificate", + "type" : "object", + "required" : [ "id", "chain", "privateKey", "caRef", "domain", "selfSigned", "ca", "valid", "autoRenew", "subject", "from", "to" ], + "properties" : { + "id" : { + "description" : "Id of the certificate", + "type" : "string", + "example" : "a string value" + }, + "chain" : { + "description" : "Certificate chain of trust in PEM format", + "type" : "string", + "example" : "a string value" + }, + "privateKey" : { + "description" : "PKCS8 private key in PEM format", + "type" : "string", + "example" : "a string value" + }, + "caRef" : { + "description" : "Reference for a CA certificate in otoroshi", + "type" : "string", + "example" : "a string value" + }, + "autoRenew" : { + "description" : "Allow Otoroshi to renew the certificate (if self signed)", + "type" : "string", + "example" : "a string value" + }, + "domain" : { + "description" : "Domain of the certificate (read only)", + "type" : "string", + "example" : "a string value" + }, + "selfSigned" : { + "description" : "Certificate is self signed read only)", + "type" : "string", + "example" : "a string value" + }, + "ca" : { + "description" : "Certificate is a CA (read only)", + "type" : "string", + "example" : "a string value" + }, + "valid" : { + "description" : "Certificate is valid (read only)", + "type" : "string", + "example" : "a string value" + }, + "subject" : { + "description" : "Subject of the certificate (read only)", + "type" : "string", + "example" : "a string value" + }, + "from" : { + "description" : "Start date of validity", + "type" : "string", + "example" : "a string value" + }, + "to" : { + "description" : "End date of validity", + "type" : "string", + "example" : "a string value" + } + } + }, + "ValidationAuthority" : { + "description" : "Settings to access a validation authority server", + "type" : "object", + "required" : [ "id", "name", "description", "url", "host", "goodTtl", "badTtl", "method", "path", "timeout", "noCache", "alwaysValid", "headers" ], + "properties" : { + "id" : { + "description" : "The id of the settings", + "type" : "string", + "example" : "a string value" + }, + "name" : { + "description" : "The name of the settings", + "type" : "string", + "example" : "a string value" + }, + "description" : { + "description" : "The description of the settings", + "type" : "string", + "example" : "a string value" + }, + "url" : { + "description" : "The URL of the server", + "type" : "string", + "example" : "a string value" + }, + "host" : { + "description" : "The host of the server", + "type" : "string", + "example" : "a string value" + }, + "goodTtl" : { + "format" : "int64", + "description" : "The TTL for valid access response caching", + "type" : "integer", + "example" : 123 + }, + "badTtl" : { + "format" : "int64", + "description" : "The TTL for invalid access response caching", + "type" : "integer", + "example" : 123 + }, + "method" : { + "description" : "The HTTP method", + "type" : "string", + "example" : "a string value" + }, + "path" : { + "description" : "The URL path", + "type" : "string", + "example" : "a string value" + }, + "timeout" : { + "format" : "int64", + "description" : "The call timeout", + "type" : "integer", + "example" : 123 + }, + "noCache" : { + "description" : "Avoid caching responses", + "type" : "boolean", + "example" : true + }, + "alwaysValid" : { + "description" : "Bypass http calls, every certificates are valids", + "type" : "boolean", + "example" : true + }, + "headers" : { + "description" : "HTTP call headers", + "additionalProperties" : { + "type" : "string" + }, + "type" : "object", + "example" : { + "key" : "value" + } + } + } + } + } +} \ No newline at end of file diff --git a/docs/manual/connectors/clevercloud.html b/docs/manual/connectors/clevercloud.html new file mode 100644 index 0000000000..2650dc49bb --- /dev/null +++ b/docs/manual/connectors/clevercloud.html @@ -0,0 +1,324 @@ + + + + +Clever Cloud * · Otoroshi + + + + + + + + + + + + + + + +
+
+ + + +
+ + + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + diff --git a/docs/manual/connectors/index.html b/docs/manual/connectors/index.html new file mode 100644 index 0000000000..b9c215cd23 --- /dev/null +++ b/docs/manual/connectors/index.html @@ -0,0 +1,329 @@ + + + + +Connectors · Otoroshi + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+ + + +
+
+

Connectors

+

Otoroshi provides a set of connectors to be used in some use cases that could be useful :

+
    +
  • rancher connector : a connector to synchronize a rancher cluster with Otoroshi on the fly
  • +
  • kubernetes connector : a connector to synchronize a kubernetes cluster with Otoroshi on the fly
  • +
  • Clever-Cloud connector : a connector to synchronize a Clever-Cloud organization with Otoroshi on the fly
  • +
+

If you want to build your own connector, Otoroshi provides a common library for synchronization. You can find the code here :

+

https://github.com/MAIF/otoroshi/tree/master/connectors/common

+ +
+
+
+
+ +
+
+ +
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + diff --git a/docs/manual/connectors/kubernetes.html b/docs/manual/connectors/kubernetes.html new file mode 100644 index 0000000000..7088e6e918 --- /dev/null +++ b/docs/manual/connectors/kubernetes.html @@ -0,0 +1,324 @@ + + + + +Kubernetes * · Otoroshi + + + + + + + + + + + + + + + +
+
+ + + +
+ + + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + diff --git a/docs/manual/connectors/rancher.html b/docs/manual/connectors/rancher.html new file mode 100644 index 0000000000..cc70157bcb --- /dev/null +++ b/docs/manual/connectors/rancher.html @@ -0,0 +1,321 @@ + + + + +Rancher * · Otoroshi + + + + + + + + + + + + + + + +
+
+ + + +
+ + + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + diff --git a/docs/manual/content-pretty.json b/docs/manual/content-pretty.json new file mode 100644 index 0000000000..62abeb2c61 --- /dev/null +++ b/docs/manual/content-pretty.json @@ -0,0 +1,408 @@ +[ + { + "name": "about.md", + "id": "/about.md", + "url": "/about.html", + "title": "About Otoroshi", + "content": "# About Otoroshi\n\nAt the beginning of 2017, we had the need to create a new environment to be able to create new \"digital\" products very quickly in an agile fashion at MAIF. Naturally we turned to PaaS solutions and chose the excellent Clever-Cloud product to run our apps. \n\nWe also chose that every feature team will have the freedom to choose its own technological stack to build its product. It was a nice move but it has also introduced some challenges in terms of homogeneity for traceability, security, logging, ... because we did not want to force library usage in the products. We could have used something like Service Mesh Pattern but the deployement model of Clever-Cloud prevented us to do it.\n\nThe right solution was to use a reverse proxy or some kind of API Gateway able to provide tracability, logging, security with apikeys, quotas, DNS as a service locator, etc. We needed something easy to use, with a human friendly UI, a nice API to extends its features, true hot reconfiguration, able to generate internal events for third party usage. A couple of solutions were available at that time, but not one seems to fit our needs, there was always something missing, too complicated for our needs or not playing well with Clever-Cloud deployment model.\n\nAt some point, we tried to write a small prototype to explore what could be our dream reverse proxy. The design was very simple, there were some rough edges but every major feature needed was there waiting to be enhanced.\n\n**Otoroshi** was born and we decided to move ahead with our hairy monster :)\n\n## Philosophy \n\nEvery OSS product build at MAIF like Izanami follow a common philosophy. \n\n* the services or API provided should be technology agnostic.\n* http first: http is the right answer to the previous quote \n* api First: The UI is just another client of the api. \n* secured: The services exposed need authentication for both humans or machines \n* event based: The services should expose a way to get notified of what happened inside. \n" + }, + { + "name": "api.md", + "id": "/api.md", + "url": "/api.html", + "title": "Admin REST API", + "content": "# Admin REST API\n\nOtoroshi provides a fully featured REST admin API to perform almost every operation possible in the Otoroshi dashboard. The Otoroshi dashbaord is just a regular consumer of the admin API.\n\nUsing the admin API, you can do whatever you want and enhance your Otoroshi instances with a lot of features that will feet your needs.\n\nOtoroshi also provides some connectors that uses the Otoroshi admin API to automate Otorshi's instances when used with stuff like containers orchestrators. For more informations about that, just go to the @ref:[third party integrations chapter](./integrations/index.md)\n\n## Swagger descriptor\n\nThe Otoroshi admin API is described using OpenAPI format and is available at :\n\nhttps://maif.github.io/otoroshi/manual/code/swagger.json\n\nEvery Otoroshi instance provides its own embedded OpenAPI descriptor at :\n\nhttp://otoroshi.oto.tools:8080/api/swagger.json\n\n## Swagger documentation\n\nYou can read the OpenAPI descriptor in a more human friendly fashion using `Swagger UI`. The swagger UI documentation of the Otoroshi admin API is available at :\n\nhttps://maif.github.io/otoroshi/swagger-ui/index.html\n\nEvery Otoroshi instance provides its own embedded OpenAPI descriptor at :\n\nhttp://otoroshi.oto.tools:8080/api/swagger/ui\n\nYou can also read the swagger UI documentation of the Otoroshi admin API below :\n\n@@@ div { .swagger-frame }\n\n\n@@@\n" + }, + { + "name": "archi.md", + "id": "/archi.md", + "url": "/archi.html", + "title": "Architecture", + "content": "# Architecture\n\nWhen we started the development of Otoroshi, we had several classical patterns in mind like `Service gateway`, `Service locator`, `Circuit breakers`, etc ...\n\nAt start we thought about providing a bunch of librairies that would be included in each microservice or app to perform these tasks. But the more we were thinking about it, the more it was feeling weird, unagile, etc, it also prevented us to use any technical stack we wanted to use. So we decided to change our approach to something more universal.\n\nWe chose to make Otoroshi the central part of our microservices system, something between a reverse-proxy, a service gateway and a service locator where each call to a microservice (even from another microservice) must pass through Otoroshi. There are multiple benefits to do that, each call can be logged, audited, monitored, integrated with a circuit breaker, etc without imposing libraries and technical stack. Any service is exposed through its own domain and we rely only on DNS to handle the service location part. Any access to a service is secured by default with an api key and is supervised by a circuit breaker to avoid cascading failures.\n\n@@@ div { .centered-img }\n\n@@@\n\nOtoroshi tries to embrace our @ref:[global philosophy](./about.md#philosophy) by providing a full featured REST admin api, a gorgeous admin dashboard written in [React](https://reactjs.org/) that uses the api, by generating traffic events, alerts events, audit events that can be consumed by several channels. Otoroshi also supports a bunch of datastores to better match with different use cases.\n\n@@@ div { .centered-img }\n\n@@@\n" + }, + { + "name": "cli.md", + "id": "/cli.md", + "url": "/cli.html", + "title": "Rust CLI", + "content": "# Rust CLI\n\n@@@ warning\nOtoroshi had a CLI written in Rust until v1.4.2 but it's deprecated now. If you are interested in maintaining this tool, just tell us ;)\n@@@\n" + }, + { + "name": "clevercloud.md", + "id": "/connectors/clevercloud.md", + "url": "/connectors/clevercloud.html", + "title": "Clever Cloud *", + "content": "# Clever Cloud *\n\n@@@ warning\nThis is an experimental connector\n@@@\n\nThe documentation is not ready yet as it's an experimental connector, but you can read more about it [here](https://github.com/MAIF/otoroshi/tree/master/connectors/clevercloud).\n" + }, + { + "name": "index.md", + "id": "/connectors/index.md", + "url": "/connectors/index.html", + "title": "Connectors", + "content": "# Connectors\n\nOtoroshi provides a set of connectors to be used in some use cases that could be useful :\n\n* [rancher connector](https://github.com/MAIF/otoroshi/tree/master/connectors/rancher) : a connector to synchronize a rancher cluster with Otoroshi on the fly\n* [kubernetes connector](https://github.com/MAIF/otoroshi/tree/master/connectors/kubernetes) : a connector to synchronize a kubernetes cluster with Otoroshi on the fly\n* [Clever-Cloud connector](https://github.com/MAIF/otoroshi/tree/master/connectors/clevercloud) : a connector to synchronize a Clever-Cloud organization with Otoroshi on the fly\n\nIf you want to build your own connector, Otoroshi provides a common library for synchronization. You can find the code here :\n\nhttps://github.com/MAIF/otoroshi/tree/master/connectors/common\n\n@@@ index\n\n* [clevercloud](./clevercloud.md)\n* [kubernetes](./kubernetes.md)\n* [rancher](./rancher.md)\n\n@@@\n" + }, + { + "name": "kubernetes.md", + "id": "/connectors/kubernetes.md", + "url": "/connectors/kubernetes.html", + "title": "Kubernetes *", + "content": "# Kubernetes *\n\n@@@ warning\nThis is an experimental connector\n@@@\n\nThe documentation is not ready yet as it's an experimental connector, but you can read more about it [here](https://github.com/MAIF/otoroshi/tree/master/connectors/kubernetes).\n" + }, + { + "name": "rancher.md", + "id": "/connectors/rancher.md", + "url": "/connectors/rancher.html", + "title": "Rancher *", + "content": "# Rancher *\n\n@@@ warning\nThis is an experimental connector\n@@@\n\nThe documentation is not ready yet as it's an experimental connector, but you can read more about it [here](https://github.com/MAIF/otoroshi/tree/master/connectors/rancher).\n" + }, + { + "name": "aws-beanstalk.md", + "id": "/deploy/aws-beanstalk.md", + "url": "/deploy/aws-beanstalk.html", + "title": "AWS - Elastic Beanstalk", + "content": "# AWS - Elastic Beanstalk\n\nNow you want to use Otoroshi on AWS. There are multiple options to deploy Otoroshi on AWS, \nfor instance :\n\n* You can deploy the [Docker image](../getotoroshi/fromdocker.md) on [Amazon ECS](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html)\n* You can create a basic [Amazon EC2](https://docs.aws.amazon.com/fr_fr/AWSEC2/latest/UserGuide/concepts.html), access it via SSH, then \ndeploy the @ref:[otoroshi.jar](../firstrun/run.md#from-jar-file) \n* Or you can use [AWS Elastic Beanstalk](https://aws.amazon.com/fr/elasticbeanstalk)\n\nIn this section we are going to cover how to deploy Otoroshi on [AWS Elastic Beanstalk](https://aws.amazon.com/fr/elasticbeanstalk). \n\n## AWS Elastic Beanstalk Overview\nUnlike Clever Cloud, to deploy an application on AWS Elastic Beanstalk, you don't link your app to your VCS repository, push your code and expect it to be built and run.\n\nAWS Elastic Beanstalk does only the run part. So you have to handle your own build pipeline, upload a Zip file containing your runnable, then AWS Elastic Beanstalk will take it from there. \n \nEg: for apps running on the JVM (Scala/Java/Kotlin) a Zip with the jar inside would suffice, for apps running in a Docker container, a Zip with the DockerFile would be enough. \n\n\n## Prepare your deployment target\nActually, there are 2 options to build your target. \n\nEither you create a DockerFile from this [Docker image](../getotoroshi/fromdocker.md), build a zip, and do all the Otoroshi custom configuration using ENVs.\n\nOr you download the @ref:[otoroshi.jar](../getotoroshi/frombinaries.md), do all the Otoroshi custom configuration using your own otoroshi.conf, and create a DockerFile that runs the jar using your otoroshi.conf. \n\nFor the second option your DockerFile would look like this :\n\n```dockerfile\nFROM openjdk:8\nVOLUME /tmp\nEXPOSE 8080\nADD otoroshi.jar otoroshi.jar\nADD otoroshi.conf otoroshi.conf\nRUN sh -c 'touch /otoroshi.jar'\nENV JAVA_OPTS=\"\"\nENTRYPOINT [ \"sh\", \"-c\", \"java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -Dconfig.file=/otoroshi.conf -jar /otoroshi.jar\" ]\n``` \n \nI'd recommend the second option.\n \nNow Zip your target (Jar + Conf + DockerFile) and get ready for deployment. \n\n## Create an Otoroshi instance on AWS Elastic Beanstalk\nFirst, go to [AWS Elastic Beanstalk Console](https://eu-west-3.console.aws.amazon.com/elasticbeanstalk/home?region=eu-west-3#/welcome), don't forget to sign in and make sure that you are in the good region (eg : eu-west-3 for Paris).\n\nHit **Get started** \n\n@@@ div { .centered-img }\n\n@@@\n\nSpecify the **Application name** of your application, Otoroshi for example.\n\n@@@ div { .centered-img }\n\n@@@\n \nChoose the **Platform** of the application you want to create, in your case use Docker.\n\nFor **Application code** choose **Upload your code** then hit **Upload**.\n\n@@@ div { .centered-img }\n\n@@@\n\nBrowse the zip created in the [previous section](#prepare-your-deployment-target) from your machine. \n\nAs you can see in the image above, you can also choose an S3 location, you can imagine that at the end of your build pipeline you upload your Zip to S3, and then get it from there (I wouldn't recommend that though).\n \nWhen the upload is done, hit **Configure more options**.\n \n@@@ div { .centered-img }\n\n@@@ \n \nRight now an AWS Elastic Beanstalk application has been created, and by default an environment named Otoroshi-env is being created as well.\n\nAWS Elastic Beanstalk can manage multiple environments of the same application, for instance environments can be (prod, preprod, expriments...). \n\nOtoroshi is a bit particular, it doesn't make much sense to have multiple environments, since Otoroshi will handle all the requests from/to downstream services regardless of the environment. \n \nAs you see in the image above, we are now configuring the Otoroshi-env, the one and only environment of Otoroshi.\n \nFor **Configuration presets**, choose custom configuration, now you have a load balancer for your environment with the capacity of at least one instance and at most four.\nI'd recommend at least 2 instances, to change that, on the **Capacity** card hit **Modify**. \n\n@@@ div { .centered-img }\n\n@@@\n\nChange the **Instances** to min 2, max 4 then hit **Save**. For the **Scaling triggers**, I'd keep the default values, but know that you can edit the capacity config any time you want, it only costs a redeploy, which will be done automatically by the way.\n \nInstances size is by default t2.micro, which is a bit small for running Otoroshi, I'd recommend a t2.medium. \nOn the **Instances** card hit **Modify**.\n\n@@@ div { .centered-img }\n\n@@@\n\nFor **Instance type** choose t2.medium, then hit **Save**, no need to change the volume size, unless you have a lot of http call faults, which means a lot more logs, in that case the default volume size may not be enough.\n\nThe default environment created for Otoroshi, for instance Otoroshi-env, is a web server environment which fits in your case, but the thing is that on AWS Elastic Beanstalk by default a web server environment for a docker-based application, runs behind an Nginx proxy.\nWe have to remove that proxy. So on the **Software** card hit **Modify**.\n \n@@@ div { .centered-img }\n\n@@@ \n \nFor **Proxy server** choose None then hit **Save**.\n\nAlso note that you can set Envs for Otoroshi in same page (see image below). \n\n@@@ div { .centered-img }\n\n@@@ \n\nTo finalise the creation process, hit **Create app** on the bottom right.\n\nThe Otoroshi app is now created, and it's running which is cool, but we still don't have neither a **datastore** nor **https**.\n \n## Create an Otoroshi datastore on AWS ElastiCache\n\nBy default Otoroshi uses non persistent memory to store it's data, Otoroshi supports many kinds of datastores. In this section we will be covering Redis datastore. \n\nBefore starting, using a datastore hosted by AWS is not at all mandatory, feel free to use your own if you like, but if you want to learn more about ElastiCache, this section may interest you, otherwise you can skip it.\n\nGo to [AWS ElastiCache](https://eu-west-3.console.aws.amazon.com/elasticache/home?region=eu-west-3#) and hit **Get Started Now**.\n\n@@@ div { .centered-img }\n\n@@@ \n\nFor **Cluster engine** keep Redis.\n\nChoose a **Name** for your datastore, for instance otoroshi-datastore.\n\nYou can keep all the other default values and hit **Create** on the bottom right of the page.\n\nOnce your Redis Cluster is created, it would look like the image below.\n\n@@@ div { .centered-img }\n\n@@@ \n\n\nFor applications in the same security group as your cluster, redis cluster is accessible via the **Primary Endpoint**. Don't worry the default security group is fine, you don't need any configuration to access the cluster from Otoroshi.\n\nTo make Otoroshi use the created cluster, you can either use Envs `APP_STORAGE=redis`, `REDIS_HOST` and `REDIS_PORT`, or set `app.storage=redis`, `app.redis.host` and `app.redis.port` in your otoroshi.conf.\n\n## Create SSL certificate and configure your domain\n\nOtoroshi has now a datastore, but not yet ready for use. \n\nIn order to get it ready you need to :\n\n* Configure Otoroshi with your domain \n* Create a wildcard SSL certificate for your domain\n* Configure Otoroshi AWS Elastic Beanstalk instance with the SSL certificate \n* Configure your DNS to redirect all traffic on your domain to Otoroshi \n \n### Configure Otoroshi with your domain\n\nYou can use ENVs or you can use a custom otoroshi.conf in your Docker container.\n\nFor the second option your otoroshi.conf would look like this :\n\n``` \n include \"application.conf\"\n http.port = 8080\n app {\n env = \"prod\"\n domain = \"mysubdomain.oto.tools\"\n rootScheme = \"https\"\n snowflake {\n seed = 0\n }\n events {\n maxSize = 1000\n }\n backoffice {\n subdomain = \"otoroshi\"\n session {\n exp = 86400000\n }\n }\n \n storage = \"redis\"\n redis {\n host=\"myredishost\"\n port=myredisport\n }\n \n privateapps {\n subdomain = \"privateapps\"\n }\n \n adminapi {\n targetSubdomain = \"otoroshi-admin-internal-api\"\n exposedSubdomain = \"otoroshi-api\"\n defaultValues {\n backOfficeGroupId = \"admin-api-group\"\n backOfficeApiKeyClientId = \"admin-client-id\"\n backOfficeApiKeyClientSecret = \"admin-client-secret\"\n backOfficeServiceId = \"admin-api-service\"\n }\n proxy {\n https = true\n local = false\n }\n }\n claim {\n sharedKey = \"myclaimsharedkey\"\n }\n }\n \n play.http {\n session {\n secure = false\n httpOnly = true\n maxAge = 2147483646\n domain = \".mysubdomain.oto.tools\"\n cookieName = \"oto-sess\"\n }\n }\n``` \n\n### Create a wildcard SSL certificate for your domain\n\nGo to [AWS Certificate Manager](https://eu-west-3.console.aws.amazon.com/acm/home?region=eu-west-3#/firstrun).\n\nBelow **Provision certificates** hit **Get started**.\n\n@@@ div { .centered-img }\n\n@@@ \n \nKeep the default selected value **Request a public certificate** and hit **Request a certificate**.\n \n@@@ div { .centered-img }\n\n@@@ \n\nPut your **Domain name**, use *. for wildcard, for instance *\\*.mysubdomain.oto.tools*, then hit **Next**.\n\n@@@ div { .centered-img }\n\n@@@ \n\nYou can choose between **Email validation** and **DNS validation**, I'd recommend **DNS validation**, then hit **Review**. \n \n@@@ div { .centered-img }\n\n@@@ \n \nVerify that you did put the right **Domain name** then hit **Confirm and request**. \n\n@@@ div { .centered-img }\n\n@@@\n \nAs you see in the image above, to let Amazon do the validation you have to add the `CNAME` record to your DNS configuration. Normally this operation takes around one day.\n \n### Configure Otoroshi AWS Elastic Beanstalk instance with the SSL certificate \n\nOnce the certificate is validated, you need to modify the configuration of Otoroshi-env to add the SSL certificate for HTTPS. \nFor that you need to go to [AWS Elastic Beanstalk applications](https://eu-west-3.console.aws.amazon.com/elasticbeanstalk/home?region=eu-west-3#/applications),\nhit **Otoroshi-env**, then on the left side hit **Configuration**, then on the **Load balancer** card hit **Modify**.\n\n@@@ div { .centered-img }\n\n@@@\n\nIn the **Application Load Balancer** section hit **Add listener**.\n\n@@@ div { .centered-img }\n\n@@@\n\nFill the popup as the image above, then hit **Add**. \n\nYou should now be seeing something like this : \n \n@@@ div { .centered-img }\n\n@@@ \n \n \nMake sure that your listener is enabled, and on the bottom right of the page hit **Apply**.\n\nNow you have **https**, so let's use Otoroshi.\n\n### Configure your DNS to redirect all traffic on your domain to Otoroshi\n \nIt's actually pretty simple, you just need to add a `CNAME` record to your DNS configuration, that redirects *\\*.mysubdomain.oto.tools* to the DNS name of Otoroshi's load balancer.\n\nTo find the DNS name of Otoroshi's load balancer go to [AWS Ec2](https://eu-west-3.console.aws.amazon.com/ec2/v2/home?region=eu-west-3#LoadBalancers:tag:elasticbeanstalk:environment-name=Otoroshi-env;sort=loadBalancerName)\n\nYou would find something like this : \n \n@@@ div { .centered-img }\n\n@@@ \n\nThere is your DNS name, so add your `CNAME` record. \n \nOnce all these steps are done, the AWS Elastic Beanstalk Otoroshi instance, would now be handling all the requests on your domain. ;) \n" + }, + { + "name": "clevercloud.md", + "id": "/deploy/clevercloud.md", + "url": "/deploy/clevercloud.html", + "title": "Clever Cloud", + "content": "# Clever Cloud\n\nNow you want to use Otoroshi on Clever Cloud. Otoroshi has been designed and created to run on Clever Cloud and a lot of choices were made because of how Clever Cloud works.\n\n## Create an Otoroshi instance on CleverCloud\n\nFirst, fork our project template on Github at https://github.com/MAIF/otoroshi-jar-clevercloud-template.\n\nIf you want to customize the build script, edit `./clevercloud/build.sh`\n\nIf you want to customize the configuration @ref:[use env. variables](../firstrun/env.md), you can use [the example provided below](#example-of-clevercloud-env-variables)\n\nCreate a new CleverCloud app based on your fork.\n\n@@@ div { .centered-img }\n\n@@@\n\nThen choose what kind of app your want to create, for Otoroshi, choose `Java + Jar`\n\n@@@ div { .centered-img }\n\n@@@\n\nNext, set up choose instance size and auto-scalling. Otoroshi can run on small instances, especially if you just want to test it.\n\n@@@ div { .centered-img }\n\n@@@\n\nFinally, choose a name for your app\n\n@@@ div { .centered-img }\n\n@@@\n\nNow you just need to customize environnment variables and add the custom build script as pre buid hook :\n\n`CC_PRE_BUILD_HOOK=./clevercloud/build.sh`\n\nat this point, you can also add other env. variables to configure Otoroshi like in [the example provided below](#example-of-clevercloud-env-variables)\n\n@@@ div { .centered-img }\n\n@@@\n\nYou can also use expert mode :\n\n@@@ div { .centered-img }\n\n@@@\n\nNow, your app is ready, don't forget to add a custom domains name on the CleverCloud app matching the Otoroshi app domain. For instance if you used domain names in env. variables like `changeme`, `changeme-admin-internal-api`, `changeme-api` on the `cleverapps.io` domain, declare `changeme.cleverapps.io`, `changeme-api.cleverapps.io`, `changeme-admin-internal-api.cleverapps.io`.\n\nYou will find the login/password tuple for first login in the app. logs.\n\n## Build and deploy Otoroshi from its source code\n\nFirst, fork our project template on Github at https://github.com/MAIF/otoroshi-clevercloud-template.\n\nIf you want to customize the build script, edit `./clevercloud/build.sh`\n\nIf you want to customize the configuration file, edit `./clevercloud/prod.conf` or @ref:[use env. variables](../firstrun/env.md)\n\nCreate a new Clever Cloud app based on your fork.\n\n@@@ div { .centered-img }\n\n@@@\n\nThen, you need to choose what kind of app your want to create, for Otoroshi, choose `Java or Scala + Play 2`\n\n@@@ div { .centered-img }\n\n@@@\n\nThen, you will be asked to choose what kind of machine you want to use. `M` instances are a good choice but you can use a less powerful ones. You can also activate auto-scaling or multi-instances to provie high availibility.\n\n@@@ div { .centered-img }\n\n@@@\n\nThen choose a name for your app :\n\n@@@ div { .centered-img }\n\n@@@\n\nNow you just need to customize environnment variables and add the custom build script as pre build hook :\n\n`CC_PRE_BUILD_HOOK=./clevercloud/build.sh`\n\nat this point, you can also add other env. variables to configure Otoroshi like in [the example provided below](#example-of-clevercloud-env-variables)\n\n@@@ div { .centered-img }\n\n@@@\n\nYou can also use expert mode :\n\n@@@ div { .centered-img }\n\n@@@\n\nNow, your app is ready, don't forget to add a custom domains name on the CleverCloud app matching the Otoroshi app domain. For instance if you used domain names in env. variables like `changeme`, `changeme-admin-internal-api`, `changeme-api` on the `cleverapps.io` domain, declare `changeme.cleverapps.io`, `changeme-api.cleverapps.io`, `changeme-admin-internal-api.cleverapps.io`.\n\nYou will find the login/password tuple for first login in the app. logs.\n\n## Example of CleverCloud env. variables\n\nYou can add more env variables to customize your Otoroshi instance like the following. Use the expert mode to copy/paste all the values in one shot :\n\n```\nAPP_ENV=prod\nAPP_STORAGE=inmemory\nAPP_DOMAIN=cleverapps.io\nAPP_ROOT_SCHEME=https\nAPP_BACKOFFICE_SUBDOMAIN=changeme\nADMIN_API_TARGET_SUBDOMAIN=changeme-admin-internal-api\nADMIN_API_EXPOSED_SUBDOMAIN=changeme-api\nADMIN_API_GROUP=psIZ0hI6eAQ2vp7DQoFfdUSfdmamtlkbXwYCe9WQHGBZMO6o5Kn1r2VVSmI61IVX\nADMIN_API_CLIENT_ID=pWkwudAifrflg8Bh\nADMIN_API_CLIENT_SECRET=ip53UuY5BFiM3wXkVUhhYrVdbsDYsANCNdRMnW3pU4I268ylsF6xxkvusS6Wv4AW\nADMIN_API_SERVICE_ID=GVQUWMZHaEYr1tCTNe9CdXOVE4DQnu1VUAx7YyXDlo5XupY3laZlWUnGyDt1vfGx\nCACHE_DEPENDENCIES=true\nCC_PRE_BUILD_HOOK=./clevercloud/build.sh\nCLAIM_SHAREDKEY=Tx1uQXW11pLNlZ25S4A08Uf8HbWDPxZ3KGSSm0B1s90gRk10PNy4d1HKY4Dnvvv5\nENABLE_METRICS=true\nJAVA_VERSION=8\nPORT=8080\nPLAY_CRYPTO_SECRET=7rNFga4AComd6ey09W9PaHqllLmPHb8WHBhlRe9xjTHOPlN15BCeSQf610cmLU1w\nSESSION_SECURE_ONLY=true\nSESSION_MAX_AGE=259200000\nSESSION_DOMAIN=changeme.cleverapps.io\nSESSION_NAME=otoroshi-session\nUSER_AGENT=otoroshi\n```\n" + }, + { + "name": "index.md", + "id": "/deploy/index.md", + "url": "/deploy/index.html", + "title": "Deploy to production", + "content": "# Deploy to production\n\nNow it's time to deploy Otoroshi in production, in this chapter we will see what kind of things you can do.\n\n@@@ index\n\n* [Clever Cloud](./clevercloud.md)\n* [AWS - Elastic Beanstalk](./aws-beanstalk.md)\n* [others](./other.md) \n* [Scaling](./scaling.md) \n\n@@@" + }, + { + "name": "other.md", + "id": "/deploy/other.md", + "url": "/deploy/other.html", + "title": "Others", + "content": "# Others\n\nOtoroshi can run wherever you want, even on a raspberry pi (Cluster^^) ;)\n\nThis section is not finished yet. So, as Otoroshi is available as a @ref:[Docker image](../getotoroshi/fromdocker.md) that you can run on any Docker compatible cloud, just go ahead and use it on cloud provider until we have more detailed documentation.\n\n## Running Otoroshi on AWS Elastic Beanstalk\n\nSee the @ref:[dedicated page to run Otoroshi on AWS Elastic Beanstalk](./aws-beanstalk.md)\n\n## Running Otoroshi on Amazon Elastic Container Service\n\nDeploy the @ref:[Docker image](../firstrun/run.md#from-docker) using [Amazon ECS](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html)\n\n## Running Otoroshi on GCE\n\nDeploy the @ref:[Docker image](../firstrun/run.md#from-docker) using [Google Compute Engine container integration](https://cloud.google.com/compute/docs/containers/deploying-containers)\n\n## Running Otoroshi on Azure\n\nDeploy the @ref:[Docker image](../firstrun/run.md#from-docker) using [Azure Container Service](https://azure.microsoft.com/en-us/services/container-service/)\n\n## Running Otoroshi on Heroku\n\nDeploy the @ref:[Docker image](../firstrun/run.md#from-docker) using [Docker integration](https://devcenter.heroku.com/articles/container-registry-and-runtime)\n\n## Running Otoroshi on CloudFoundry\n\nDeploy the @ref:[Docker image](../firstrun/run.md#from-docker) using [Docker integration](https://docs.cloudfoundry.org/adminguide/docker.html)\n\n## Running Otoroshi on your own infrastructure\n\nAs Otoroshi is a [Play Framework](https://www.playframework.com) application, you can read the doc about putting a `Play` app in production.\n\nhttps://www.playframework.com/documentation/2.6.x/ProductionConfiguration\n\nDownload the latest @ref:[Otoroshi distribution](../getotoroshi/frombinaries.md), unzip it, customize it and run it.\n" + }, + { + "name": "scaling.md", + "id": "/deploy/scaling.md", + "url": "/deploy/scaling.html", + "title": "Scaling Otoroshi", + "content": "# Scaling Otoroshi\n\n## Using multiple instances with a front load balancer\n\nOtoroshi has been designed to work with multiple instances. If you already have an infrastructure using frontal load balancing, you just have to declare Otoroshi instances as the target of all domain names handled by Otoroshi\n\n## Using master / workers mode of Otoroshi\n\nYou can read everything about it in @ref:[the clustering section](../topics/clustering.md) of the documentation.\n\n## Using IPVS\n\nYou can use [IPVS](https://en.wikipedia.org/wiki/IP_Virtual_Server) to load balance layer 4 traffic directly from the Linux Kernel to multiple instances of Otoroshi. You can find example of configuration [here](http://www.linuxvirtualserver.org/VS-DRouting.html) \n\n## Using DNS Round Robin\n\nYou can use [DNS round robin technique](https://en.wikipedia.org/wiki/Round-robin_DNS) to declare multiple A records under the domain names handled by Otoroshi.\n\n## Using software L4/L7 load balancers\n\nYou can use software L4 load balancers like NGINX or HAProxy to load balance layer 4 traffic directly from the Linux Kernel to multiple instances of Otoroshi.\n\nNGINX L7\n: @@snip [nginx-http.conf](../snippets/nginx-http.conf) \n\nNGINX L4\n: @@snip [nginx-tcp.conf](../snippets/nginx-tcp.conf) \n\nHA Proxy L7\n: @@snip [haproxy-http.conf](../snippets/haproxy-http.conf) \n\nHA Proxy L4\n: @@snip [haproxy-tcp.conf](../snippets/haproxy-tcp.conf) \n\n## Using a custom TCP load balancer\n\nYou can also use any other TCP load balancer, from a hardware box to a small js file like\n\ntcp-proxy.js\n: @@snip [tcp-proxy.js](../snippets/tcp-proxy.js) \n\ntcp-proxy.rs\n: @@snip [tcp-proxy.rs](../snippets/proxy.rs) \n\n" + }, + { + "name": "embedding.md", + "id": "/embedding.md", + "url": "/embedding.html", + "title": "Embedding Otoroshi", + "content": "# Embedding Otoroshi\n\nOtoroshi provides an API to start Otoroshi instances programmatically from any JVM app.\n\n## Getting the dependencies\n\nYou can get the Otoroshi dependency from bintray\n\nSbt\n: @@snip [build.sbt](./snippets/build.sbt)\n\nGradle\n: @@snip [build.gradle](./snippets/build.gradle)\n\n## Starting Otoroshi\n\nNow just instanciate an Otoroshi proxy with the configuration you like and you will be able to control it using the internal APIs of Otoroshi.\n\nScala\n: @@snip [embed.scala](./snippets/embed.scala)\n\nJava\n: @@snip [embed.java](./snippets/embed.java)\n" + }, + { + "name": "features.md", + "id": "/features.md", + "url": "/features.html", + "title": "Features ", + "content": "# Features \n\nAll the features supported by **Otoroshi** are listed below\n\n* Dynamic changes at runtime without full reload \n* Can proxy any HTTP/HTTP2 server\n* Can proxy websockets\n* Full featured admin Rest Api to control Otoroshi the way you want. Included, Swagger descriptor\n* Gorgeous React Web UI\n* Full end-to-end streaming of HTTP requests and responses\n* Completely non blocking and async internals\n* @ref:[Official Docker image](./getotoroshi/fromdocker.md)\n* @ref:[Multi backend datastore support](./firstrun/datastore.md)\n * Redis\n * In memory\n * Cassandra (experimental support)\n * Mongo (experimental support)\n * LevelDB (not suitable for production usage)\t\t\n* @ref:[Service is private (Api key access) by default with exclusions](./usage/2-services.md)\n* @ref:[Support wildcard domain names per service](./usage/2-services.md)\n* @ref:[Support routing headers for a service (ie. for service versioning)](./usage/2-services.md#service-meta)\n* @ref:[Support adding headers for a service request (ie. add Authorization header)](./usage/2-services.md#service-meta)\n* @ref:[Support custom html errors templates per service](./usage/2-services.md#custom-error-templates)\n* @ref:[Configurable circuit breaker and retries (with backoff) on network errors per service](./usage/2-services.md#service-circuit-breaker)\n* @ref:[Round Robin load balancing per service](./usage/2-services.md#targets)\n* @ref:[Services can be declared private (private apps) using an Auth0 domain](./usage/2-services.md#service-flags)\n* @ref:[Support for canary mode per service](./usage/2-services.md#canary-mode)\n* @ref:[Configurable health check per service](./usage/2-services.md#service-health-check)\n* @ref:[Support IP addresses blacklist per service (with wildcard support)](./usage/2-services.md#service-settings)\n* @ref:[Support IP addresses whitelist per service (with wildcard support)](./usage/2-services.md#service-settings)\n* @ref:[Support mutiple Api keys per service](./usage/3-apikeys.md)\n* @ref:[Support configurable throttling quota per Api key](./usage/3-apikeys.md#quotas)\n* @ref:[Support configurable daily quota per Api key](./usage/3-apikeys.md#quotas)\n* @ref:[Support configurable monthly quota per Api key](./usage/3-apikeys.md#quotas)\n* @ref:[Api keys are authorized for a group of services](./usage/3-apikeys.md)\n* @ref:[Api keys can be passed with custom headers, `Authorization: Basic ` headers, `Authorization: Bearer` or Cookies](./usage/3-apikeys.md)\n* @ref:[Add current Api key quotas usage in response headers](./usage/3-apikeys.md#quotas)\n* @ref:[Add current latencies in response headers](./usage/3-apikeys.md#quotas)\n* @ref:[Support service duplication through web UI](./usage/2-services.md#service-flags)\n* @ref:[Maintenance page per service](./usage/2-services.md#service-flags)\n* @ref:[Build page per service](./usage/2-services.md#service-flags)\n* @ref:[Force HTTPS usage per service](./usage/2-services.md#service-flags)\n* @ref:[Force secured exchanges with exclusions per service](./usage/2-services.md#service-flags)\n* @ref:[OpenAPI documentation displayed through web UI per service](./usage/2-services.md#service-settings)\n* @ref:[Live metrics per service (Rest)](./usage/4-monitor.md#service-live-stats)\n* @ref:[Metrics and analytics per service (if used with the Elastic connector)](./usage/4-monitor.md#service-analytics)\n* @ref:[Global metrics and analytics (if used with the Elastic connector)](./usage/7-metrics.md)\n* @ref:[Global audit log on admins actions](./usage/6-audit.md#audit-trail)\n* @ref:[Global alert log on admins actions](./usage/6-audit.md#alerts)\n* @ref:[Internal events can be sent outside using webhooks or Kafka topic](./setup/dangerzone.md#analytics-settings)\n* @ref:[Audit events can be sent outside using webhooks or Kafka topic](./setup/dangerzone.md#analytics-settings)\n* @ref:[Alerts events can be sent outside using webhooks or Kafka topic](./setup/dangerzone.md#analytics-settings)\n* @ref:[Alerts can be send to people by email using Mailgun](./integrations/mailgun.md)\n* @ref:[Support global IP addresses blacklist (with wildcard support)](./setup/dangerzone.md#whitelist-blacklist-settings)\n* @ref:[Support global IP addresses whitelist (with wildcard support)](./setup/dangerzone.md#whitelist-blacklist-settings)\n* @ref:[Support global endless responses for IP addresses (128 Gb of 0 for each response)](./setup/dangerzone.md#whitelist-blacklist-settings)\n* @ref:[Support global throttling quota](./setup/dangerzone.md#global-throttling-settings)\n* @ref:[Support global throttling quota per IP address (with wildcard support)](./setup/dangerzone.md#global-throttling-settings)\n* @ref:[Support global max number of concurrent connections](./setup/dangerzone.md#commons-settings)\n* Support global request tracing\n* @ref:[Support full JSON export of the reverse proxy state](./usage/8-importsexports.md#full-export)\n* @ref:[Support full JSON import of the reverse proxy state](./usage/8-importsexports.md#full-import)\n* @ref:[Support initial internal state import from JSON local file](./firstrun/configfile.md#db-configuration)\n* @ref:[Support initial internal state import from JSON file over HTTP](./firstrun/configfile.md#db-configuration)\n* @ref:[Enforce incoming JWT token verification and transformation](./topics/jwt-verifications.md)\n* @ref:[Introduce HTTP level chaos engineering pratices with the Snow Monkey](./topics/snow-monkey.md)\n* @ref:[Native support for service mesh architectures](./topics/service-mesh.md)\n* @ref:[Global live metrics](./setup/index.md#first-login)\n* @ref:[Send metrics to a StatsD/Datadog agent](./integrations/statsd.md)\n* @ref:[Advanced CleverCloud integration (create services from CleverCloud apps)](./integrations/clevercloud.md)\n* @ref:[Support admin login with any auth. module](./usage/9-auth.md)\n* @ref:[Support admin login with FIDO U2F device](./setup/admin.md#create-admin-user-with-u2f-device-login)\n* @ref:[Dynamic SSL termination](./topics/ssl.md)\n* @ref:[Rust CLI running on MacOS, Linux and Windows](./cli.md)\n* @ref:[Connectors to various HTTP services providers](./connectors/index.md)\n * [generic connector API](https://github.com/MAIF/otoroshi/tree/master/connectors/common)\n * @ref:[Clever Cloud](./connectors/clevercloud.md)\n * @ref:[Rancher](./connectors/rancher.md)\n * @ref:[Kubernetes](./connectors/kubernetes.md)\n" + }, + { + "name": "configfile.md", + "id": "/firstrun/configfile.md", + "url": "/firstrun/configfile.html", + "title": "Config. with files", + "content": "# Config. with files\n\nThere is a lot of things you can configure in Otoroshi. By default, Otoroshi provides a configuration that should be enough for testing purpose. But you'll likely need to update this configuration when you'll need to move into production.\n\nIn this page, any configuration property can be set at runtime using a `-D` flag when launching Otoroshi like\n\n```sh\njava -Dhttp.port=8080 -jar otoroshi.jar\n```\n\nor\n\n```sh\n./bin/otoroshi -Dhttp.port=8080 \n```\n\n## Common configuration\n\n| name | type | default value | description |\n| ---- |:----:| -------------- | ----- |\n| `app.domain` | string | \"oto.tools\" | the domain on which Otoroshi UI/API is be exposed|\n| `app.rootScheme` | string | \"http\" | the scheme on which Otoroshi is exposed, either \"http\" or \"https\" |\n| `app.snowflake.seed` | number | 0 | this number will is used to generate unique ids across the cluster. Each Otorshi instance must have a unique seed. |\n| `app.events.maxSize` | number | 1000 | max number of analytic and alert events stored locally |\n| `app.backoffice.exposed` | boolean | true | does the current Otoroshi instance exposed a backoffice ui|\n| `app.backoffice.subdomain` | string | \"otoroshi\" | the subdomain on wich Otoroshi backoffice will be served |\n| `app.backoffice.session.exp` | number | 86400000 | the number of seconds before the Otoroshi backoffice session expires |\n| `app.privateapps.subdomain` | string | \"privateapps\" | the subdomain on which private apps UI are served |\n| `app.privateapps.session.exp` | number | 86400000 | the number of seconds before the private apps session expires |\n| `app.claim.sharedKey` | string | \"secret\" | the shared secret used for signing the JWT token passed between Otoroshi and backend services |\n| `app.webhooks.size` | number | 100 | number of events sent at most when calling one of the analytics webhooks |\n| `app.throttlingWindow` | number | 10 | time window (in seconds) used to compute throttling quotas for ApiKeys |\n\n## Admin API configuration\n\nWhen Otoroshi starts for the first time, its datastore is empty. As Otoroshi uses Otoroshi to expose its admin REST API, you'll have to provide the details for the admin API exposition. **This part is super important** because if you go to production with the default values, your Otoroshi server won't be secured anymore.\n\n@@@ warning\nYOU HAVE TO CUSTOMIZE THE FOLLOWING VALUES BEFORE GOING TO PRODUCTION !!\n@@@\n\nSome of the following terms will seem obscure to you, but you will learn their meaning in the following chapters :)\n\n| name | type | default value | description |\n| ---- |:----:| -------------- | ----- |\n| `app.adminapi.exposed` | boolean | true | does the current Otoroshi instance expose an admin API |\n| `app.adminapi.targetSubdomain` | string | \"otoroshi-admin-internal-api\" | the subdomain on wich admin API call will be redirected from `app.adminapi.exposedSubdomain` |\n| `app.adminapi.exposedSubdomain` | string | \"otoroshi-api\" | the subdomain on wich the Otoroshi admin API will be exposed |\n| `app.adminapi.defaultValues.backOfficeGroupId` | string | \"admin-api-group\" | the name of the service groups that will contain the service descriptors for the Otoroshi admin API |\n| `app.adminapi.defaultValues.backOfficeApiKeyClientId` | string | \"admin-api-apikey-id\" | the client id of the Otoroshi admin API apikey |\n| `app.adminapi.defaultValues.backOfficeApiKeyClientSecret` | string | \"admin-api-apikey-secret\" | the client secret of the Otoroshi admin API apikey |\n| `app.adminapi.defaultValues.backOfficeServiceId` | string | \"admin-api-service\" | the id of the service descriptors for the Otoroshi admin API |\n| `app.adminapi.proxy.https` | boolean | false | whether or not the current Otoroshi instance serves its content over https. This setting is useful for the backoffice UI to access Otoroshi admin API |\n| `app.adminapi.proxy.local` | boolean | true | whether or not the admin API is accessible through `127.0.0.1`. This setting is useful for the backoffice UI to access Otoroshi admin API |\n\n## DB configuration\n\nAs Otoroshi supports multiple datastores, you'll have to provide some details about how to connect/configure it.\n\n| name | type | default value | description |\n| ---- |:----:| -------------- | ----- |\n| `app.storage` | string | \"inmemory\" | what kind of storage engine you want to use. Possible values are `inmemory`, `leveldb`, `redis`, `cassandra`, `mongo` |\n| `app.importFrom` | string | | a file path or a URL to an Otoroshi export file. If the datastore is empty on startup, this file will be used to import data to the empty DB |\n| `app.importFromHeaders` | array | [] | a list of `:` separated header to use if the `app.importFrom` setting is a URL |\n| `app.initialData` | object |  | object representing Otoroshi internal data as exported from danger zone so you don't need a config file and a data import file |\n| `app.redis.host` | string | \"localhost\" | the host of the redis server |\n| `app.redis.port` | number | 6379 | the port of the redis server |\n| `app.redis.slaves` | array | [] | the redis slaves lists |\n| `app.leveldb.path` | string | \"./leveldb\" | the path where levelDB files will be written |\n| `app.cassandra.hosts` | string | \"127.0.0.1\" | the host of the cassandra server |\n| `app.cassandra.host` | string | \"127.0.0.1\" | the list of cassandra hosts |\n| `app.cassandra.port` | number | 9042 | the port of the cassandra servers |\n| `app.mongo.uri` | string | \"mongodb://localhost:27017/default\" | the mongo URI following Mongo semantic https://docs.mongodb.com/manual/reference/connection-string/ |\n\n## Headers configuration\n\nOtoroshi uses a fair amount of http headers in order to work properly. The name of those headers are customizable to fit your needs.\n\n| name | type | default value | description |\n| ---- |:----:| -------------- | ----- |\n| `otoroshi.headers.trace.label` | string | \"Otoroshi-Viz-From-Label\" | header to pass request tracing informations |\n| `otoroshi.headers.trace.from` | string | \"Otoroshi-Viz-From\" | header to pass request tracing informations (ip address) |\n| `otoroshi.headers.trace.parent` | string | \"Otoroshi-Parent-Request\" | header to pass request tracing informations (parent request id) |\n| `otoroshi.headers.request.adminprofile` | string | \"Otoroshi-Admin-Profile\" | header to pass admin name when the admin API is called from the Otoroshi backoffice |\n| `otoroshi.headers.request.clientid` | string | \"Otoroshi-Client-Id\" | header to pass apikey client id |\n| `otoroshi.headers.request.clientsecret` | string | \"Otoroshi-Client-Secret\" | header to pass apikey client secret |\n| `otoroshi.headers.request.id` | string | \"Otoroshi-Request-Id\" | header containing the id of the current request |\n| `otoroshi.headers.response.proxyhost` | string | \"Otoroshi-Proxied-Host\" | header containing the proxied host |\n| `otoroshi.headers.response.error` | string | \"Otoroshi-Error\" | header containing whether or not the request generated an error |\n| `otoroshi.headers.response.errormsg` | string | \"Otoroshi-Error-Msg\" | header containing error message if some |\n| `otoroshi.headers.response.proxylatency` | string | \"Otoroshi-Proxy-Latency\" | header containing the current latency induced by Otoroshi |\n| `otoroshi.headers.response.upstreamlatency` | string | \"Otoroshi-Upstream-Latency\" | header containing the current latency from Otoroshi to service backend |\n| `otoroshi.headers.response.dailyquota` | string | \"Otoroshi-Daily-Calls-Remaining\" | header containing the number of remaining daily call (apikey) |\n| `otoroshi.headers.response.monthlyquota` | string | \"Otoroshi-Monthly-Calls-Remaining\" | header containing the number of remaining monthly call (apikey) |\n| `otoroshi.headers.comm.state` | string | \"Otoroshi-State\" | header containing a random value for secured mode |\n| `otoroshi.headers.comm.stateresp` | string | \"Otoroshi-State-Resp\" | header containing a random value for secured mode |\n| `otoroshi.headers.comm.claim` | string | \"Otoroshi-Claim\" | header containing a JWT token for secured mode |\n| `otoroshi.headers.healthcheck.test` | string | \"Otoroshi-Health-Check-Logic-Test\" | header containing a logic test for healthcheck |\n| `otoroshi.headers.healthcheck.testresult` | string | \"Otoroshi-Health-Check-Logic-Test-Result\" | header containing the result of a logic test for healthcheck |\n| `otoroshi.headers.jwt.issuer` | string | \"Otoroshi\" | the name of the issuer for the JWT token |\n| `otoroshi.headers.canary.tracker` | string | \"Otoroshi-Canary-Id\" | header containing the ID of the canary session if enabled |\n\n## Play specific configuration\n\nAs Otoroshi is a [Play app](https://www.playframework.com/), you should take a look at [Play configuration documentation](https://www.playframework.com/documentation/2.6.x/Configuration) to tune its internal configuration\n\n| name | type | default value | description |\n| ---- |:----:| -------------- | ----- |\n| `http.port` | number | 8080 | the http port used by Otoroshi. You can use 'disabled' as value if you don't want to use http |\n| `https.port` | number | disabled | the https port used by Otoroshi. You can use 'disabled' as value if you don't want to use https |\n| `http2.enabled` | boolean | false | whether or not http2 is enabled on the Otoroshi server. You need to configure https (listed bellow) to be able to use it |\n| `play.http.secret.key` | string | \"secret\" | the secret used to sign Otoroshi session cookie |\n| `play.http.session.secure` | boolean | false | whether or not the Otoroshi backoffice session will be served over https only |\n| `play.http.session.httpOnly` | boolean | true | whether or not the Otoroshi backoffice session will be accessible from Javascript |\n| `play.http.session.maxAge` | number | 259200000 | the number of seconds before Otoroshi backoffice session expired |\n| `play.http.session.domain` | string | \".oto.tools\" | the domain on which the Otoroshi backoffice session is authorized |\n| `play.http.session.cookieName` | string | \"otoroshi-session\" | the name of the Otoroshi backoffice session |\n| `play.ws.play.ws.useragent` | string | \"Otoroshi\" | the user agent sent by Otoroshi if not present on the original http request |\n| `play.server.https.keyStore.path` | string | | the path to the keystore containing the private key and certificate, if not provided generates a keystore for you |\n| `play.server.https.keyStore.type` | string | JKS | the key store type, defaults to JKS |\n| `play.server.https.keyStore.password` | string | '' | the password, defaults to a blank password |\n| `play.server.https.keyStore.algorithm` | string | | the key store algorithm, defaults to the platforms default algorithm |\n\n## More config. options\n\nSee https://github.com/MAIF/otoroshi/blob/master/otoroshi/conf/base.conf and https://github.com/MAIF/otoroshi/blob/master/otoroshi/conf/application.conf\n\nif you want to configure https on your Otoroshi server, just read [PlayFramework documentation about it](https://www.playframework.com/documentation/2.6.x/ConfiguringHttps)\n\n## Example of configuration file\n\n```conf\ninclude \"application.conf\"\n\nhttp.port = 8080\n\napp {\n storage = \"leveldb\"\n importFrom = \"./my-state.json\"\n env = \"prod\"\n domain = \"oto.tools\"\n rootScheme = \"http\"\n snowflake {\n seed = 0\n }\n events {\n maxSize = 1000\n }\n backoffice {\n subdomain = \"otoroshi\"\n session {\n exp = 86400000\n }\n }\n privateapps {\n subdomain = \"privateapps\"\n session {\n exp = 86400000\n }\n }\n adminapi {\n targetSubdomain = \"otoroshi-admin-internal-api\"\n exposedSubdomain = \"otoroshi-api\"\n defaultValues {\n backOfficeGroupId = \"admin-api-group\"\n backOfficeApiKeyClientId = \"admin-api-apikey-id\"\n backOfficeApiKeyClientSecret = \"admin-api-apikey-secret\"\n backOfficeServiceId = \"admin-api-service\"\n }\n }\n claim {\n sharedKey = \"mysecret\"\n }\n leveldb {\n path = \"./leveldb\"\n }\n}\n\nplay.http {\n session {\n secure = false\n httpOnly = true\n maxAge = 2592000000\n domain = \".oto.tools\"\n cookieName = \"oto-sess\"\n }\n}\n```\n" + }, + { + "name": "datastore.md", + "id": "/firstrun/datastore.md", + "url": "/firstrun/datastore.html", + "title": "Choose your datastore", + "content": "# Choose your datastore\n\nRight now, Otoroshi supports multiple datastore.\n\nYou can choose one datastore over another depending on your use case.\n\nAvailable datastores are the following :\n\n* in memory\n* redis\n* cassandra (experimental support)\n* mongodb (experimental support)\n* levelDB (not suitable for production usage)\n\nThe **levelDB** datastore is pretty handy for testing purposes, but is not supposed to be used in production mode.\n\nThe **in-memory** datastore is kind of interesting... It can be used for testing purposes, but it is also a good candidate for production because of its fastness. But in that case, you need to provide a way to feed the deployed in-memory instances after the initial boot. In a future release (i.e. not yet :D), we will provide a master/workers slave based on Otoroshi in memory instances and kafka (see https://github.com/MAIF/otoroshi/issues/8 for more details about the feature).\n\nThe **redis** datastore is quite nice when you want to easily deploy several Otoroshi instances.\n\nIf you need a datastore more scalable than redis, then you can use the **cassandra** datastore or the **mongodb** datastore.\n\nWe plan to add more datastores support in the future :)\n\n@@@ div { .centered-img }\n\n@@@\n" + }, + { + "name": "env.md", + "id": "/firstrun/env.md", + "url": "/firstrun/env.html", + "title": "Config. with ENVs", + "content": "# Config. with ENVs\n\nNow that you know @ref:[how to configure Otoroshi with the config. file](./configfile.md) every property in the following block can be overriden by an environment variable (an env. variable is written like `${?ENV_VARIABLE}`).\n\n```\napp.storage = ${?APP_STORAGE}\napp.importFrom = ${?APP_IMPORT_FROM}\napp.domain = ${?APP_DOMAIN}\napp.rootScheme = ${?APP_ROOT_SCHEME}\napp.throttlingWindow = ${?THROTTLING_WINDOW}\napp.snowflake.seed = ${?INSTANCE_NUMBER}\napp.events.maxSize = ${?MAX_EVENTS_SIZE}\napp.backoffice.exposed = ${?APP_BACKOFFICE_EXPOSED}\napp.backoffice.subdomain = ${?APP_BACKOFFICE_SUBDOMAIN}\napp.backoffice.session.exp = ${?APP_BACKOFFICE_SESSION_EXP}\napp.privateapps.subdomain = ${?APP_PRIVATEAPPS_SUBDOMAIN}\napp.privateapps.session.exp = ${?APP_PRIVATEAPPS_SESSION_EXP}\napp.adminapi.exposed = ${?ADMIN_API_EXPOSED}\napp.adminapi.targetSubdomain = ${?ADMIN_API_TARGET_SUBDOMAIN}\napp.adminapi.exposedSubdomain = ${?ADMIN_API_EXPOSED_SUBDOMAIN}\napp.adminapi.defaultValues.backOfficeGroupId = ${?ADMIN_API_GROUP}\napp.adminapi.defaultValues.backOfficeApiKeyClientId = ${?ADMIN_API_CLIENT_ID}\napp.adminapi.defaultValues.backOfficeApiKeyClientSecret = ${?ADMIN_API_CLIENT_SECRET}\napp.adminapi.defaultValues.backOfficeServiceId = ${?ADMIN_API_SERVICE_ID}\napp.adminapi.proxy.https = ${?ADMIN_API_HTTPS}\napp.adminapi.proxy.local = ${?ADMIN_API_LOCAL}\napp.claim.sharedKey = ${?CLAIM_SHAREDKEY}\napp.webhooks.size = ${?WEBHOOK_SIZE}\napp.redis.host = ${?REDIS_HOST}\napp.redis.port = ${?REDIS_PORT}\napp.redis.password = ${?REDIS_PASSWORD}\napp.redis.windowSize = ${?REDIS_WINDOW_SIZE}\napp.redis.useScan = ${?REDIS_USE_SCAN}\napp.inmemory.windowSize = ${?INMEMORY_WINDOW_SIZE}\napp.leveldb.windowSize = ${?LEVELDB_WINDOW_SIZE}\napp.leveldb.path = ${?LEVELDB_PATH}\napp.cassandra.windowSize = ${?CASSANDRA_WINDOW_SIZE}\napp.cassandra.hosts = ${?CASSANDRA_HOSTS}\napp.cassandra.host = ${?CASSANDRA_HOST}\napp.cassandra.port = ${?CASSANDRA_PORT}\napp.elastic.url = ${?ELASTIC_URL}\napp.elastic.user = ${?ELASTIC_USER}\napp.elastic.password = ${?ELASTIC_PASSWORD}\napp.elastic.index = ${?ELASTIC_INDEX}\napp.elastic.type = ${?ELASTIC_TYPE}\napp.mongo.uri = ${?MONGO_URI}\nhttp.port = ${?PORT}\nhttps.port = ${?HTTPS_PORT}\nhttp2.enabled = ${?HTTP2_ENABLED}\nplay.http.secret.key = ${?PLAY_CRYPTO_SECRET}\nplay.http.session.secure = ${?SESSION_SECURE_ONLY}\nplay.http.session.maxAge = ${?SESSION_MAX_AGE}\nplay.http.session.domain = ${?SESSION_DOMAIN}\nplay.http.session.cookieName = ${?SESSION_NAME}\nplay.ws.play.ws.useragent=${?USER_AGENT}\n```\n" + }, + { + "name": "host.md", + "id": "/firstrun/host.md", + "url": "/firstrun/host.html", + "title": "Setup your hosts", + "content": "# Setup your hosts\n\nBy default, Otoroshi starts with domain `oto.tools` that targets `127.0.0.1`. Of course you can change the domain, you have to add the values in your `/etc/hosts` file according to the setting you put in Otoroshi configuration\n\n* `app.domain` => `oto.tools`\n* `app.backoffice.subdomain` => `otoroshi`\n* `app.privateapps.subdomain` => `privateapps`\n* `app.adminapi.exposedSubdomain` => `otoroshi-api`\n* `app.adminapi.targetSubdomain` => `otoroshi-admin-internal-api`\n\nfor instance if you want to change the default domain and use something like `otoroshi.mydomain.org`, then start otoroshi like \n\n```sh\njava -Dapp.domain=mydomain.org -jar otoroshi.jar\n```\n\n@@@ warning\nOtoroshi cannot be accessed using `http://127.0.0.1:8080` or `http://localhost:8080` because Otoroshi uses Otoroshi to serve it's own UI and API. When otoroshi starts with an empty database, it will create a service descriptor for that using `app.domain` and the settings listed on this page and in the * [Config. with files page](./configfile.md) that serve Otoroshi API and UI on `http://otoroshi-api.${app.domain}` and `http://otoroshi.${app.domain}`.\nOnce the descriptor is saved in database, if you want to change `app.domain`, you'll have to edit the descriptor in the database or restart Otoroshi with an empty database.\n@@@\n" + }, + { + "name": "index.md", + "id": "/firstrun/index.md", + "url": "/firstrun/index.html", + "title": "First run", + "content": "# First run\n\nNow that you have your own distro of Otoroshi, it's time to run it. \n\nBut before doing so, you'll have to make some choices about some essential stuff in order to have your own customized version of Otoroshi.\n\nLet's start with the datastore\n\n\n@@@ index\n\n* [choose a datastore](./datastore.md)\n* [use custom config file](./configfile.md)\n* [use ENV](./env.md)\n* [initial state](./initialstate.md)\n* [Hosts](./host.md)\n* [Run](./run.md)\n\n@@@" + }, + { + "name": "initialstate.md", + "id": "/firstrun/initialstate.md", + "url": "/firstrun/initialstate.html", + "title": "Import initial state", + "content": "# Import initial state\n\nNow you are almost ready to run Otoroshi for the first time, but maybe you want to import data from previous Otoroshi installation in your current datastore.\n\nTo do that, you need to add the `app.importFrom` setting to the Otoroshi configuration (of `$APP_IMPORT_FROM` env).\n\nIt can be a file path or a URL\n\n## Example of export\n\n```json\n{\n \"config\": {\n \"lines\": [\"prod\"], \n \"limitConcurrentRequests\": true,\n \"maxConcurrentRequests\": 500,\n \"useCircuitBreakers\": true,\n \"apiReadOnly\": false,\n \"registerFromCleverHook\": false,\n \"u2fLoginOnly\": true,\n \"ipFiltering\": {\n \"whitelist\": [],\n \"blacklist\": []\n },\n \"throttlingQuota\": 100000,\n \"perIpThrottlingQuota\": 500,\n \"analyticsEventsUrl\": null,\n \"analyticsWebhooks\": [],\n \"alertsWebhooks\": [],\n \"alertsEmails\": [],\n \"endlessIpAddresses\": []\n },\n \"admins\": [],\n \"simpleAdmins\": [\n {\n \"username\": \"admin@otoroshi.io\",\n \"password\": \"xxxxxxxxxxxxxxxxx\",\n \"label\": \"Otoroshi Admin\",\n \"createdAt\": 1493971715708\n }\n ],\n \"serviceGroups\": [\n {\n \"id\": \"default\",\n \"name\": \"default-group\",\n \"description\": \"The default group\"\n },\n {\n \"id\": \"admin-api-group\",\n \"name\": \"Otoroshi Admin Api group\",\n \"description\": \"No description\"\n }\n ],\n \"apiKeys\": [\n {\n \"clientId\": \"admin-api-apikey-id\",\n \"clientSecret\": \"admin-api-apikey-secret\",\n \"clientName\": \"Otoroshi Backoffice ApiKey\",\n \"authorizedGroup\": \"admin-api-group\",\n \"enabled\": true,\n \"throttlingQuota\": 10000000,\n \"dailyQuota\": 10000000,\n \"monthlyQuota\": 10000000,\n \"metadata\": {}\n }\n ],\n \"serviceDescriptors\": [\n {\n \"id\": \"admin-api-service\",\n \"groupId\": \"admin-api-group\",\n \"name\": \"otoroshi-admin-api\",\n \"env\": \"prod\",\n \"domain\": \"oto.tools\",\n \"subdomain\": \"otoroshi-api\",\n \"targets\": [\n {\n \"host\": \"localhost:8080\",\n \"scheme\": \"http\"\n }\n ],\n \"root\": \"/\",\n \"enabled\": true,\n \"privateApp\": false,\n \"forceHttps\": false,\n \"maintenanceMode\": false,\n \"buildMode\": false,\n \"enforceSecureCommunication\": true,\n \"publicPatterns\": [],\n \"privatePatterns\": [],\n \"additionalHeaders\": {\n \"Host\": \"otoroshi-admin-internal-api.oto.tools\"\n },\n \"matchingHeaders\": {},\n \"ipFiltering\": {\n \"whitelist\": [],\n \"blacklist\": []\n },\n \"api\": {\n \"exposeApi\": false\n },\n \"healthCheck\": {\n \"enabled\": false,\n \"url\": \"/\"\n },\n \"metadata\": {}\n }\n ],\n \"errorTemplates\": []\n}\n```\n" + }, + { + "name": "run.md", + "id": "/firstrun/run.md", + "url": "/firstrun/run.html", + "title": "Run Otoroshi", + "content": "# Run Otoroshi\n\nNow you are ready to run Otoroshi. You can run the following command with some tweaks depending on the way you want to configure Otoroshi. If you want to pass a custom configuration file, use the `-Dconfig.file=/path/to/file.conf` flag in the following commands.\n\n## From .zip file\n\n```sh\nunzip otoroshi-dist.zip\ncd otoroshi-vx.x.x\n./bin/otoroshi\n```\n\n## From .jar file\n\nFor Java 8 & Java 11\n\n```sh\njava -jar otoroshi.jar\n```\n\n## From docker\n\n```sh\ndocker run -p \"8080:8080\" maif/otoroshi:1.4.8-dev\n```\n\nYou can also pass useful args like :\n\n```\ndocker run -p \"8080:8080\" otoroshi -Dconfig.file=/usr/app/otoroshi/conf/otoroshi.conf -Dlogger.file=/usr/app/otoroshi/conf/otoroshi.xml\n```\n\nIf you want to provide your own config file, you can read @ref:[the documentation about config files](../firstrun/configfile.md).\n\nYou can also provide some ENV variable using the `--env` flag to customize your Otoroshi instance.\n\nThe list of possible env variables is available @ref:[here](../firstrun/env.md).\n\nYou can use a volume to provide configuration like :\n\n```sh\ndocker run -p \"8080:8080\" -v \"$(pwd):/usr/app/otoroshi/conf\" maif/otoroshi\n```\n\nYou can also use a volume if you choose to use `leveldb` datastore like :\n\n```sh\ndocker run -p \"8080:8080\" -v \"$(pwd)/leveldb:/usr/app/otoroshi/leveldb\" maif/otoroshi -Dapp.storage=leveldb\n```\n\nYou can also use a volume if you choose to use exports files :\n\n```sh\ndocker run -p \"8080:8080\" -v \"$(pwd):/usr/app/otoroshi/imports\" maif/otoroshi -Dapp.importFrom=/usr/app/otoroshi/imports/export.json\n```\n\n## Run examples\n\n```sh\n$ java \\\n -Xms2G \\\n -Xmx8G \\\n -Dhttp.port=8080 \\\n -Dapp.importFrom=/home/user/otoroshi.json \\\n -Dconfig.file=/home/user/otoroshi.conf \\\n -jar ./otoroshi.jar\n\n[warn] otoroshi-in-memory-datastores - Now using InMemory DataStores\n[warn] otoroshi-env - The main datastore seems to be empty, registering some basic services\n[warn] otoroshi-env - Importing from: /home/user/otoroshi.json\n[info] play.api.Play - Application started (Prod)\n[info] p.c.s.AkkaHttpServer - Listening for HTTP on /0:0:0:0:0:0:0:0:8080\n```\n\nIf you choose to start Otoroshi without importing existing data, Otoroshi will create a new admin user and print the login details in the log. When you will log into the admin dashboard, Otoroshi will ask you to create another account to avoid security issues.\n\n```sh\n$ java \\\n -Xms2G \\\n -Xmx8G \\\n -Dhttp.port=8080 \\\n -jar otoroshi.jar\n\n[warn] otoroshi-in-memory-datastores - Now using InMemory DataStores\n[warn] otoroshi-env - The main datastore seems to be empty, registering some basic services\n[warn] otoroshi-env - You can log into the Otoroshi admin console with the following credentials: admin@otoroshi.io / HHUsiF2UC3OPdmg0lGngEv3RrbIwWV5W\n[info] play.api.Play - Application started (Prod)\n[info] p.c.s.AkkaHttpServer - Listening for HTTP on /0:0:0:0:0:0:0:0:8080\n```\n" + }, + { + "name": "frombinaries.md", + "id": "/getotoroshi/frombinaries.md", + "url": "/getotoroshi/frombinaries.html", + "title": "From binaries", + "content": "# From binaries\n\nIf you want to download the last version of Otoroshi and its CLI, you can grab them from the release page of the Otoroshi github page :\n\nGo to https://github.com/MAIF/otoroshi/releases and get the last version of the `otoroshi-dist.zip` file or `otoroshi.jar` file\n" + }, + { + "name": "fromdocker.md", + "id": "/getotoroshi/fromdocker.md", + "url": "/getotoroshi/fromdocker.html", + "title": "From docker", + "content": "# From docker\n\nIf you're a Docker aficionado, Otoroshi is provided as a Docker image that your can pull directly from Official repos.\n\nfirst, fetch the last Docker image of Otoroshi :\n\n```sh\ndocker pull maif/otoroshi:1.4.18\n# or \ndocker pull maif/otoroshi:latest\n# or \ndocker pull maif/otoroshi:jdk8-1.4.18\n# or \ndocker pull maif/otoroshi:jdk11-1.4.18\n# or \ndocker pull maif/otoroshi:jdk12-1.4.18\n# or \ndocker pull maif/otoroshi:jdk13-1.4.18\n# or \ndocker pull maif/otoroshi:jdk14-1.4.18\n```" + }, + { + "name": "fromsources.md", + "id": "/getotoroshi/fromsources.md", + "url": "/getotoroshi/fromsources.html", + "title": "From sources", + "content": "# From sources\n\nto build Otoroshi from sources, you need the following tools :\n\n* git\n* JDK 8\n* SBT\n* node\n* yarn\n\nOnce you've installed all those tools, go to the [Otoroshi github page](https://github.com/MAIF/otoroshi) and clone the sources :\n\n```sh\ngit clone https://github.com/MAIF/otoroshi.git --depth=1\n```\n\nthen you need to run the `build.sh` script to build the documentation, the React UI and the server :\n\n```sh\nsh ./scripts/build.sh\n```\n\nand that's all, you can grab your Otoroshi package at `otoroshi/target/scala-2.12/otoroshi` or `otoroshi/target/universal/`.\n\nFor those who want to build only parts of Otoroshi, read the following.\n\n## Build the documentation only\n\nGo to the `documentation` folder and run :\n\n```sh\nsbt ';clean;paradox'\n```\n\nThe documentation is located at `documentation/target/paradox/site/main/`\n\n## Build the React UI\n\nGo to the `otoroshi/javascript` folder and run :\n\n```sh\nyarn install\nyarn build\n```\n\nYou will find the JS bundle at `otoroshi/public/javascripts/bundle/bundle.js`.\n\n## Build the Otoroshi server\n\nGo to the `otoroshi` folder and run :\n\n```sh\nsbt ';clean;compile;dist;assembly'\n```\n\nYou will find your Otoroshi package at `otoroshi/target/scala-2.12/otoroshi` or `otoroshi/target/universal/`.\n" + }, + { + "name": "index.md", + "id": "/getotoroshi/index.md", + "url": "/getotoroshi/index.html", + "title": "Get Otoroshi", + "content": "# Get Otoroshi\n\nThere are several ways to get Otoroshi to run it on your system.\n\nLet's start with a good old build from sources :)\n\n@@@ index\n\n* [from sources](./fromsources.md)\n* [from binaries](./frombinaries.md)\n* [from docker](./fromdocker.md)\n\n@@@" + }, + { + "name": "index.md", + "id": "/index.md", + "url": "/index.html", + "title": "Otoroshi", + "content": "# Otoroshi\n\n**Otoroshi** is a layer of lightweight api management on top of a modern http reverse proxy written in Scala and developped by the MAIF OSS team that can handle all the calls to and between your microservices without service locator and let you change configuration dynamicaly at runtime.\n\n\n> *The Otoroshi is a large hairy monster that tends to lurk on the top of the torii gate in front of Shinto shrines. It's a hostile creature, but also said to be the guardian of the shrine and is said to leap down from the top of the gate to devour those who approach the shrine for only self-serving purposes.*\n\n@@@ div { .centered-img }\n[![Build Status](https://travis-ci.org/MAIF/otoroshi.svg?branch=master)](https://travis-ci.org/MAIF/otoroshi) [![Join the chat at https://gitter.im/MAIF/otoroshi](https://badges.gitter.im/MAIF/otoroshi.svg)](https://gitter.im/MAIF/otoroshi?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [ ![Download](https://img.shields.io/github/release/MAIF/otoroshi.svg) ](hhttps://github.com/MAIF/otoroshi/releases/download/v1.4.18/otoroshi.jar)\n@@@\n\n@@@ div { .centered-img }\n\n@@@\n\n## Installation\n\nYou can download the latest build of Otoroshi as a [fat jar](https://github.com/MAIF/otoroshi/releases/download/v1.4.18/otoroshi.jar), as a [zip package](https://github.com/MAIF/otoroshi/releases/download/v1.4.18/otoroshi-dist.zip) or as a @ref:[docker image](./getotoroshi/fromdocker.md).\n\nYou can install and run Otoroshi with this little bash snippet\n\n```sh\ncurl -L -o otoroshi.jar 'https://github.com/MAIF/otoroshi/releases/download/v1.4.18/otoroshi.jar'\njava -jar otoroshi.jar\n```\n\nor using docker\n\n```sh\ndocker run -p \"8080:8080\" maif/otoroshi:1.4.18\n```\n\nnow open your browser to http://otoroshi.oto.tools:8080/, **log in with the credential generated in the logs** and explore by yourself, if you want better instructions, just go to the @ref:[Quick Start](./quickstart.md) or directly to the @ref:[installation instructions](./getotoroshi/index.md)\n\n## Documentation\n\n* @ref:[About Otoroshi](./about.md)\n* @ref:[Architecture](./archi.md)\n* @ref:[Features](./features.md)\n* @ref:[Try Otoroshi in 5 minutes](./quickstart.md)\n* @ref:[Video tutorials](./videos.md)\n* @ref:[Get Otoroshi](./getotoroshi/index.md)\n* @ref:[First run](./firstrun/index.md)\n* @ref:[Setup Otoroshi](./setup/index.md)\n* @ref:[Using Otoroshi](./usage/index.md)\n* @ref:[Third party Integrations](./integrations/index.md)\n* @ref:[Detailed topics](./topics/index.md)\n* @ref:[Embedding Otoroshi](./embedding.md)\n* @ref:[Admin REST API](./api.md)\n* @ref:[Rust CLI](./cli.md)\n* @ref:[Deploy to production](./deploy/index.md)\n* @ref:[Connectors](./connectors/index.md)\n\n## Discussion\n\nJoin the [Otoroshi](https://gitter.im/MAIF/otoroshi) channel on the [MAIF Gitter](https://gitter.im/MAIF)\n\n## Sources\n\nThe sources of Otoroshi are available on [Github](https://github.com/MAIF/otoroshi).\n\n## Logo\n\nYou can find the official Otoroshi logo [on GitHub](https://github.com/MAIF/otoroshi/blob/master/resources/otoroshi-logo.png). The Otoroshi logo has been created by François Galioto ([@fgalioto](https://twitter.com/fgalioto))\n\n## Changelog\n\nEvery release, along with the migration instructions, is documented on the [Github Releases](https://github.com/MAIF/otoroshi/releases) page.\n\n## Patrons\n\nThe work on Otoroshi was funded by MAIF with the help of the community.\n\n## Licence\n\nOtoroshi is Open Source and available under the [Apache 2 License](https://opensource.org/licenses/Apache-2.0)\n\n@@@ index\n\n* [About Otoroshi](about.md)\n* [Architecture](archi.md)\n* [Features](features.md)\n* [Quickstart](quickstart.md)\n* [Videos](videos.md)\n* [Get otoroshi](getotoroshi/index.md)\n* [First run](firstrun/index.md)\n* [Setup](setup/index.md)\n* [Using Otoroshi](usage/index.md)\n* [Integrations](integrations/index.md)\n* [Detailed topics](topics/index.md)\n* [Admin REST API](api.md)\n* [Embedding Otoroshi](./embedding.md)\n* [Official Rust CLI](cli.md)\n* [Deploy to production](deploy/index.md)\n* [Connectors](connectors/index.md)\n\n@@@\n" + }, + { + "name": "analytics.md", + "id": "/integrations/analytics.md", + "url": "/integrations/analytics.html", + "title": "Analytics", + "content": "# Analytics\n\nEach action and request on Otoroshi creates events that can be sent outside of Otoroshi for further usage. Those events can be sent using a webhook and/or through a Kafka topic.\n\n## Push events to Elasticsearch\n\nYou can use elastic search to store otoroshi events. To do this you have to configure the access to elasticsearch from `settings (cog icon) / Danger Zone` and expand the `Analytics: Elastic cluster (write)` section.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Read events from Elasticsearch\n\nYou can use elastic search to store otoroshi events. To do this you have to configure the access to elasticsearch from `settings (cog icon) / Danger Zone` and expand the `Analytics: Elastic dashboard datasource (read)` section.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Push events to WebHooks\n\nGo to `settings (cog icon) / Danger Zone` and expand the `Analytics: Webhooks` section.\n\n@@@ div { .centered-img }\n\n@@@\n\nHere you can configure the URL of the webhook and its headers if needed.\n\n## Push events to Kafka\n\nEvents can also be sent through a Kafka topic. Go to `settings (cog icon) / Danger Zone` and expand the `Analytics: Kafka` section.\n\n@@@ div { .centered-img }\n\n@@@\n\nFill the form, default values for topic names are :\n\n* `otoroshi-alerts`\n* `otoroshi-analytics`\n* `otoroshi-audits`\n\n@@@ warning\nIf you use trustore/keystore to access your kafka instances, the paths should be absolute and refers to host paths.\n@@@\n" + }, + { + "name": "auth0.md", + "id": "/integrations/auth0.md", + "url": "/integrations/auth0.html", + "title": "Auth0", + "content": "# Auth0\n\nYou can use Auth0 to log into Otoroshi's backoffice and Otoroshi's private apps.\n\nGo to `settings (cog icon) / Danger Zone` and expand the `Backoffice Auth0 settings` and `Private apps Auth0 settings` sections.\n\n@@@ div { .centered-img }\n\n@@@\n\nNow, you can fill the following fields :\n\n* `Client Id`\n* `Client Service`\n* `Domain`\n\nFor the `Callback URL` fields, use something like\n\n```\nhttps://otoroshi.oto.tools/backoffice/auth0/callback\nhttps://privateapps.oto.tools/privateapps/auth0/callback\n```\n\nOf course, you need to replace `otoroshi.oto.tools` and `privateapps.oto.tools` with your own domain and sub-domains. Don't forget to customize the callback URLs in your Auth0 backoffice too.\n\nNow if you logout, you will see the Auth0 option on the login screen\n\n@@@ div { .centered-img }\n\n@@@\n" + }, + { + "name": "clevercloud.md", + "id": "/integrations/clevercloud.md", + "url": "/integrations/clevercloud.html", + "title": "Clever Cloud", + "content": "# Clever Cloud\n\nOtoroshi provides an integration with Clever Cloud to create easily services based on application deployed on your Clever Cloud account.\nGo to `settings (cog icon) / Danger Zone` and expand the `CleverCloud settings` section.\n\n@@@ div { .centered-img }\n\n@@@\n\nFill the form with your CleverCloud credentials (https://www.clever-cloud.com/doc/clever-cloud-apis/cc-api/) and your CleverCloud `organization id`.\n\nOnce it's done, you will see a new menu in the side bar.\n\n@@@ div { .centered-img }\n\n@@@\n\nIf you click on it, you'll see a page listing all your apps deployed on Clever Cloud with buttons to create new services with the app as the target.\n\n@@@ div { .centered-img }\n\n@@@\n\nYou will also see a new button in the `Target` section of services to attach Clever Cloud applications as target for a service.\n\n@@@ div { .centered-img }\n\n@@@\n" + }, + { + "name": "index.md", + "id": "/integrations/index.md", + "url": "/integrations/index.html", + "title": "Third party Integrations", + "content": "# Third party Integrations\n\nOtoroshi provides some settings to interact with some third party systems.\n\n@@@ index\n\n* [Analytics](./analytics.md)\n* [Mailgun](./mailgun.md)\n* [StatsD / Datadog](./statsd.md)\n* [clevercloud](./clevercloud.md)\n\n@@@\n" + }, + { + "name": "mailgun.md", + "id": "/integrations/mailgun.md", + "url": "/integrations/mailgun.html", + "title": "Mailgun", + "content": "# Mailgun\n\nIf you want to receive Otoroshi alert by emails, you have to configure Otoroshi with your Mailgun credentials. Go to `settings (cog icon) / Danger Zone` and expand the `Mailgun settings` section.\n\n@@@ div { .centered-img }\n\n@@@\n\nFill the form with provided information on the `domain informations` page on Mailgun located at https://app.mailgun.com/app/domains/my.domain.\n\nThen, expand the `Alert settings` section and add email addresses separated by comma in the `Alert emails` field. **Don't forget to save.**\n\n@@@ div { .centered-img }\n\n@@@\n" + }, + { + "name": "statsd.md", + "id": "/integrations/statsd.md", + "url": "/integrations/statsd.html", + "title": "StatsD / Datadog", + "content": "# StatsD / Datadog\n\nOtoroshi provides a StatsD integration to monitor some technical metrics across all your Otoroshi instances.\nGo to `settings (cog icon) / Danger Zone` and expand the `Statsd settings` section.\n\n@@@ div { .centered-img }\n\n@@@\n\nAdd the host and port of the Statsd agent on your system.\nIf you're using Datadog, don't forget to check the `Datadog` switch.\n" + }, + { + "name": "quickstart.md", + "id": "/quickstart.md", + "url": "/quickstart.html", + "title": "Try Otoroshi in 5 minutes", + "content": "# Try Otoroshi in 5 minutes\n\nwhat you will need :\n\n* JDK 8\n* curl\n* 5 minutes of free time\n\nIf you don't/can't have these tools on your machine, you can start a sandboxed environment using here with the following command\n\n```sh\ndocker run -p \"8080:8080\" -it maif/otoroshi bash\n```\n\nor you can also try Otoroshi online with Google Cloud Shell if you're a user of the Google Cloud Platform\n\n@@@ div { .centered-img }\n[![Open in Cloud Shell](http://gstatic.com/cloudssh/images/open-btn.svg)](https://console.cloud.google.com/cloudshell/open?git_repo=https%3A%2F%2Fgithub.com%2Fmathieuancelin%2Fotoroshi-tutorial&page=shell&tutorial=tutorial.md)\n@@@\n\n## The elevator pitch\n\nOtoroshi is an awesome reverse proxy built with Scala that handles all the calls to and between your microservices without service locator and lets you change configuration dynamically at runtime.\n\n## I like sh but I really want to see the UI\n\nAs Otoroshi uses Otoroshi to serve its own admin UI and admin API, you won't be able to access the admin UI on `http://localhost:8080`. Otoroshi needs a domain name to know that you want to access the admin UI. By default, the admin UI is exposed on `http://otoroshi.oto.tools:8080`. Of course you can @ref:[configure](./firstrun/configfile.md#common-configuration) the domain of the subdomain. To configure access to the admin, just go to the [UI section of the quickstart](#what-about-the-ui).\n\n## Now some sh :)\n\n```sh\ncurl -L -o otoroshi.jar https://github.com/MAIF/otoroshi/releases/download/v1.4.18/otoroshi.jar\ncurl -L -o otoroshicli https://github.com/MAIF/otoroshi/releases/download/v1.4.18/linux-otoroshicli\n\nchmod +x otoroshicli\n\n# Run the Otoroshi server on Java 8\njava -jar otoroshi.jar &\n\n# Run the Otoroshi server on Java 9 and 10\njava --add-modules java.xml.bind -jar otoroshi.jar &\n\n# Check if admin api works\n./otoroshicli services all\n./otoroshicli apikeys all\n./otoroshicli groups all\n\n# Create a service that will proxy call to https://freegeoip.net through http://ip.geo.com:8080\n./otoroshicli services create --group default --name geo-ip-api --env prod \\\n --domain geo.com --subdomain ip --root /json/ --target https://freegeoip.net \\\n --public-pattern '/.*' --no-force-https\n\n# Then test it\n./otoroshicli tryout call \"http://127.0.0.1:8080/\" -X GET -H 'Host: ip.geo.com'\n# works with curl -X GET -H 'Host: ip.geo.com' \"http://127.0.0.1:8080/\" | jqn\n\n# Run 3 new microservices in 3 new terminal processes\n./otoroshicli tryout serve 9901\n./otoroshicli tryout serve 9902\n./otoroshicli tryout serve 9903\n\n# Create a service that will loadbalance between these 3 microservices and serves them through\n# http://api.hello.com:8080\n./otoroshicli services create --group default --id hello-api --name hello-api \\\n --env prod --domain hello.com --subdomain api --root / \\\n --target \"http://127.0.0.1:9901\" \\\n --target \"http://127.0.0.1:9902\" \\\n --public-pattern '/.*' --no-force-https --client-retries 3\n\n# Then test it multiple time to observe loadbalancing\n# You can also define '127.0.0.1 api.hello.com' in your /etc/hosts file and test it in your browser\n./otoroshicli tryout call \"http://127.0.0.1:8080/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\nsudo echo \"127.0.0.1 api.hello.com\" >> /etc/hosts\nopen \"http://api.hello.com:8080/\"\n\n# Then add a new target\n./otoroshicli services add-target hello-api --target=\"http://127.0.0.1:9903\"\n\n# Then test it multiple time to observe loadbalancing\n./otoroshicli tryout call \"http://127.0.0.1:8080/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\n\n# Then kill one of the microservices and test it multiple time to observe loadbalancing\n./otoroshicli tryout call \"http://127.0.0.1:8080/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\n\n# Then kill a second microservices and test it multiple time to observe loadbalancing\n./otoroshicli tryout call \"http://127.0.0.1:8080/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\n\n# Then kill the last microservices and test it to observe connection error\n./otoroshicli tryout call \"http://127.0.0.1:8080/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\n\n# Then delete your service\n./otoroshicli services delete hello-api\n```\n\n## What about the UI\n\nGo to http://otoroshi.oto.tools:8080/ and **log in with the credential generated in the logs** during first startup ;-)\n\nIf you want to know more about Otoroshi, you should continue reading the documentation starting with @ref:[how to get Otoroshi](./getotoroshi/index.md)\n\n## I don't have JDK 8 on my machine but I have Docker :)\n\nIf you want to use Docker, just follow these instructions\n\n```sh\n# here be careful, if you want to change the OTOROSHI_PORT, change the same value in the `otoroshicli.toml` config file\nexport OTOROSHI_PORT=8080\nexport LOCAL_IP_ADDRESS=999.999.999.999 # use your real local ip address here\n\ncurl -L -o otoroshicli https://github.com/MAIF/otoroshi/releases/download/v1.4.18/linux-otoroshicli\n\nchmod +x otoroshicli \n\ndocker run -p \"$OTOROSHI_PORT:8080\" maif/otoroshi &\n\n# Check if admin api works\n./otoroshicli services all\n./otoroshicli apikeys all\n./otoroshicli groups all\n\n# Create a service that will proxy call to https://freegeoip.net through http://ip.geo.com:$OTOROSHI_PORT\n./otoroshicli services create --group default --name geo-ip-api --env prod \\\n --domain geo.com --subdomain ip --root /json/ --target https://freegeoip.net \\\n --public-pattern '/.*' --no-force-https\n\n# Then test it\n./otoroshicli tryout call \"http://127.0.0.1:$OTOROSHI_PORT/\" -X GET -H 'Host: ip.geo.com'\n# works with curl -X GET -H 'Host: ip.geo.com' \"http://127.0.0.1:$OTOROSHI_PORT/\" | jqn\n\n# Run 3 new microservices in 3 new terminal processes\n./otoroshicli tryout serve 9901\n./otoroshicli tryout serve 9902\n./otoroshicli tryout serve 9903\n\n# Create a service that will loadbalance between these 3 microservices and serves them through\n# http://api.hello.com:$OTOROSHI_PORT\n./otoroshicli services create --group default --id hello-api --name hello-api \\\n --env prod --domain hello.com --subdomain api --root / \\\n --target \"http://$LOCAL_IP_ADDRESS:9901\" \\\n --target \"http://$LOCAL_IP_ADDRESS:9902\" \\\n --public-pattern '/.*' --no-force-https --client-retries 3\n\n# Then test it multiple time to observe loadbalancing\n# You can also define '127.0.0.1 api.hello.com' in your /etc/hosts file and test it in your browser\n./otoroshicli tryout call \"http://127.0.0.1:$OTOROSHI_PORT/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\nsudo echo \"127.0.0.1 api.hello.com\" >> /etc/hosts\nopen \"http://api.hello.com:$OTOROSHI_PORT/\"\n\n# Then add a new target\n./otoroshicli services add-target hello-api --target=\"http://$LOCAL_IP_ADDRESS:9903\"\n\n# Then test it multiple time to observe loadbalancing\n./otoroshicli tryout call \"http://127.0.0.1:$OTOROSHI_PORT/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\n\n# Then kill one of the microservices and test it multiple time to observe loadbalancing\n./otoroshicli tryout call \"http://127.0.0.1:$OTOROSHI_PORT/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\n\n# Then kill a second microservices and test it multiple time to observe loadbalancing\n./otoroshicli tryout call \"http://127.0.0.1:$OTOROSHI_PORT/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\n\n# Then kill the last microservices and test it to observe connection error\n./otoroshicli tryout call \"http://127.0.0.1:$OTOROSHI_PORT/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\n\n# Then delete your service\n./otoroshicli services delete hello-api\n```\n" + }, + { + "name": "admin.md", + "id": "/setup/admin.md", + "url": "/setup/admin.html", + "title": "Manage admin users", + "content": "# Manage admin users\n\n## Create admin user after the first run\n\nClick on the `Create an admin user` warning popup, or go to `settings (cog icon) / Admins`.\n\n@@@ div { .centered-img }\n\n@@@\n\nYou will see the list of registered admin users.\n\n@@@ div { .centered-img }\n\n@@@\n\nNow, enter informations about the new admin you want to create.\n\n@@@ div { .centered-img }\n\n@@@\n\nClick on `Register Admin`.\n\n@@@ div { .centered-img }\n\n@@@\n\nNow, you can discard the generated admin, confirm, then logout, login with the admin user you have just created and the danger popup will go away\n\n@@@ div { .centered-img }\n\n@@@\n\n## Create admin user with U2F device login\n\nGo to `settings (cog icon) / Admins`.\n\n@@@ div { .centered-img }\n\n@@@\n\nEnter informations about the new admin you want to create.\n\n@@@ div { .centered-img }\n\n@@@\n\nClick on `Register FIDO U2F Admin`.\n\nOtoroshi will ask you to plug your FIDO U2F device and touch it to complete registration.\n\n@@@ div { .centered-img }\n\n@@@\n\n@@@ warning\nTo be able to use FIDO U2F devices, Otoroshi must be served over https\n@@@\n\n## Discard admin user\n\nGo to `settings (cog icon) / Admins`, at the bottom of the page, you will see a list of admin users that you can discard. Just click on the `Discard User` button on the right side of the row and confirm that you actually want to discard an admin user.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Admin sessions management\n\nGo to `settings (cog icon) / Admins sessions`, you will see a list of active admin user sessions\n\n@@@ div { .centered-img }\n\n@@@\n\nYou can either discard single sessions one by one using the `Discard Session` on each targeted row of the list or discard all active sessions using the `Discard all sessions` button at the top of the page.\n" + }, + { + "name": "dangerzone.md", + "id": "/setup/dangerzone.md", + "url": "/setup/dangerzone.html", + "title": "Configure the Danger zone", + "content": "# Configure the Danger zone\n\nNow that you have an actual admin account, go to `setting (cog icon) / Danger Zone` in order to configure your Otoroshi instance.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Commons settings\n\nThis part allows you to configure various things :\n\n* `No Auth0 login` => allow you to disabled Auth0 login to the Otoroshi admin dashboard\n* `API read only` => disable `writes` on the Otoroshi admin api\n* `Use HTTP streaming` => use http streaming for each response. It should always be true\n* `Use circuit breakers` => allow usage of circuit breakers for each service\n* `Digitus medius` => change the character of endless HTTP responses from `0` to `🖕`\n* `Limit concurrent requests` => allow you to specify a max number of concurrent requests on an Otoroshi instance to avoid overloading\n* `Max concurrent requests` => max allowed number of concurrent requests on an Otoroshi instance to avoid overloading\n* `Max HTTP/1.0 response size` => max size of an HTTP/1.0 responses, because they are memory mapped\n* `Max local events` => number of events stored localy (alerts and audits)\n* `lines` => at least one (`prod`). for other, it will allow you to declare urls like `service.line.domain.tld`. For prod it will be `service.domain.tld`\n\n@@@ div { .centered-img }\n\n@@@\n\n## Whitelist / blacklist settings\n\nOtoroshi is capable of filtering request by ip address, allowing or blocking requests.\n\nOtoroshi also provides a fun feature called `Endless HTTP responses`. If you put an ip address in that field, then, for any http request on Otoroshi, every response will be 128 GB of `0`.\n\n@@@ div { .centered-img }\n\n@@@\n\n@@@ note\nNote that you may provide ip address with wildcard like the following `42.42.*.42` or `42.42.42.*` or `42.42.*.*`\n@@@\n\n## Global throttling settings\n\nOtoroshi is capable of managing throttling at a global level. Here you can configure number of authorized requests per second on a single Otoroshi instance and the number of authorized request per second for a unique ip address.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Analytics settings\n\nOne on the major features of Otoroshi is being able of generating internal events. Those events are not stored in Otoroshi's datastore but can be sent using `WebHooks`. You can configure those `WebHooks` from the `Danger Zone`.\n\nOtoroshi is also capable of reading some analytics and displays it from another MAIF product called `Omoïkane`. As Omoikane is not publicly available yet, is capable of storing events in an [Elastic](https://www.elastic.co/) cluster. For more information about analytics and what it does, just go to the @ref:[detailed chapter](../integrations/analytics.md)\n\n## Kafka settings\n\nOne on the major features of Otoroshi is being able of generating internal events. These events are not stored in Otoroshi's datastore but can be sent using a [Kafka message broker](https://kafka.apache.org/). You can configure Kafka access from the `Danger Zone`.\n\nBy default, Otoroshi's alert events will be sent on `otoroshi-alerts` topic, Otoroshi's audit events will be sent on `otoroshi-audits` topic and Otoroshi's traffic events will be sent on `otoroshi-analytics` topic.\n\n@@@ warning\nKeystore and truststore paths are optional local path on the server hosting Otoroshi\n@@@\n\n@@@ div { .centered-img }\n\n@@@\n\nFor more information about Kafka integration and what it does, just go to the @ref:[detailed chapter](../integrations/analytics.md)\n\n## Alerts settings\n\nEach time a dangerous action or something unusual is performed on Otoroshi, it will create an alert and store it. You can be notified for each of these alerts using `WebHooks` or emails. To do so, just add the `WebHook` URL and optional headers in the `Danger Zone` or any email address you want (you can add more than one email address).\n\n@@@ div { .centered-img }\n\n@@@\n\n## StatsD settings\n\nOtoroshi is capable of sending internal metrics to a StatsD agent. Just put the host and port of you StatsD agent in the `Danger Zone` to collect these metrics. If you using [Datadog](https://www.datadoghq.com), don't forget to check the dedicated button :)\n\n@@@ div { .centered-img }\n\n@@@\n\nFor more information about StatsD integration and what it does, just go to the @ref:[detailed chapter](../integrations/statsd.md)\n\n## Auth0 settings\n\nIt is possible to configure Otoroshi to allow admin users to log in through Auth0. Otoroshi also provides a feature called `Private apps.` that allows you to force login to an Auth0 domain before accessing an app. You can create an Auth0 client (https://manage.auth0.com/#/clients) for each of those features (admin users login and private apps login) and customize it with any rule you want (don't forget allowed callbacks, like `http://otoroshi.oto.tools:8080/backoffice/auth0/callback` and `http://privateapps.oto.tools:8080/backoffice/auth0/callback`).\n\nOnce you're done, go to the settings of each client (https://manage.auth0.com/#/clients/xxxxxxxxxxxxxxxx/settings) to get the information needed for the `Danger Zone`.\n\n@@@ div { .centered-img }\n\n@@@\n\n@@@ div { .centered-img }\n\n@@@\n\nFor more information about Auth0 integration and what it does, just go to the @ref:[detailed chapter](../integrations/auth0.md)\n\n## Mailgun settings\n\nIf you want to send emails for every alert generated by Otoroshi, you need to configure your Mailgun credentials in the `Danger Zone`. These parameters are provided in you Mailgun domain dashboard (i.e. https://app.mailgun.com/app/domains/my.domain.oto.tools) in the information section.\n\n@@@ div { .centered-img }\n\n@@@\n\nFor more information about Mailgun integration and what it does, just go to the @ref:[detailed chapter](../integrations/mailgun.md)\n\n## CleverCloud settings\n\nAs we built our products to run on Clever-Cloud, Otoroshi has a close integration with Clever-Cloud. In this section of `Danger Zone` you can configure how to access Clever-Cloud API.\n\nTo generate the needed value, please refers to [Clever-Cloud documentation](https://www.clever-cloud.com/doc/clever-cloud-apis/cc-api/)\n\n@@@ div { .centered-img }\n\n@@@\n\nFor more information about Clever-Cloud integration and what it does, just go to the @ref:[detailed chapter](../integrations/clevercloud.md)\n\n## Import / exports and panic mode\n\nFor more details about imports and exports, please go to the @ref:[dedicated chapter](../usage/8-importsexports.md)\n\nAbout panic mode, it's an unusual feature that allows you to discard all current admin. sessions, allows only admin users with U2F devices to log back, and pass the API in read-only mode. Only a person who has access to Otoroshi's datastore will be able to turn it back on.\n\n@@@ div { .centered-img }\n\n@@@\n" + }, + { + "name": "index.md", + "id": "/setup/index.md", + "url": "/setup/index.html", + "title": "Setup Otoroshi", + "content": "# Setup Otoroshi\n\nNow that Otoroshi is running, you are ready to log into the Otoroshi admin dashboard and setup your instance. Just go to :\n\nhttp://otoroshi.oto.tools:8080\n\nand you will see the following page\n \n@@@ div { .centered-img }\n\n@@@\n\nnow click on the login button and you will see the login page\n\n@@@ div { .centered-img }\n\n@@@\n\n@@@ warning\nUse the credentials generated in Otoroshi **logs** during **first run**.\n@@@\n\n@@@ div { .centered-img #first-login-example }\n\n@@@\n\n(of course, you can change this url dependending on the configuration you provided to Otoroshi).\n\nOnce logged in, the first screen you'll see should look like :\n\n@@@ div { .centered-img #first-login }\n\n@@@\n\nAs you can see, Otoroshi is not really happy about you being logged with a generated admin account.\n\nBut we will fix that in the next chapter\n\n@@@ index\n\n* [create admins](./admin.md)\n* [configure danger zone](./dangerzone.md)\n\n@@@\n" + }, + { + "name": "toc.md", + "id": "/toc.md", + "url": "/toc.html", + "title": "Table of contents", + "content": "# Table of contents\n\n@@toc\n\n" + }, + { + "name": "clustering.md", + "id": "/topics/clustering.md", + "url": "/topics/clustering.html", + "title": "Otoroshi clustering", + "content": "# Otoroshi clustering\n\nOtoroshi can work as a cluster by default as you can spin many Otoroshi servers using the same datastore or datastore cluster. In that case any instance is capable of serving services, Otoroshi admin UI, Otoroshi admin API, etc.\n\nBut sometimes, this is not enough. So Otoroshi provides an additional clustering model named `Leader / Workers` where there is a leader cluster ([control plane](https://en.wikipedia.org/wiki/Control_plane)), composed of Otoroshi instances backed by a datastore like Redis, Cassandra or Mongo, that is in charge of all `writes` to the datastore through Otoroshi admin UI and API, and a worker cluster ([data plane](https://en.wikipedia.org/wiki/Forwarding_plane)) composed of horizontally scalable Otoroshi instances, backed by a super fast in memory datastore, with the sole purpose of routing traffic to your services based on data synced from the leader cluster. With this distributed Otoroshi version, you can reach your goals of high availability, scalability and security.\n\nOtoroshi clustering only uses http internally (right now) to make communications between leaders and workers instances so it is fully compatible with PaaS providers like [Clever-Cloud](https://www.clever-cloud.com/en/) that only provide one external port for http traffic.\n\n@@@ div { .centered-img }\n\n\n*Fig. 1: Simplified view*\n@@@\n\n@@@ div { .centered-img }\n\n\n*Fig. 2: Deployment view*\n@@@\n\n## Cluster configuration\n\n```hocon\notoroshi {\n cluster {\n mode = \"leader\" # can be \"off\", \"leader\", \"worker\"\n compression = 4 # compression of the data sent between leader cluster and worker cluster. From -1 (disabled) to 9\n leader {\n name = ${?CLUSTER_LEADER_NAME} # name of the instance, if none, it will be generated\n urls = [\"http://127.0.0.1:8080\"] # urls to contact the leader cluster\n host = \"otoroshi-api.oto.tools\" # host of the otoroshi api in the leader cluster\n clientId = \"apikey-id\" # otoroshi api client id\n clientSecret = \"secret\" # otoroshi api client secret\n cacheStateFor = 4000 # state is cached during (ms)\n }\n worker {\n name = ${?CLUSTER_WORKER_NAME} # name of the instance, if none, it will be generated\n retries = 3 # number of retries when calling leader cluster\n timeout = 2000 # timeout when calling leader cluster\n state {\n retries = ${otoroshi.cluster.worker.retries} # number of retries when calling leader cluster on state sync\n pollEvery = 10000 # interval of time (ms) between 2 state sync\n timeout = ${otoroshi.cluster.worker.timeout} # timeout when calling leader cluster on state sync\n }\n quotas {\n retries = ${otoroshi.cluster.worker.retries} # number of retries when calling leader cluster on quotas sync\n pushEvery = 2000 # interval of time (ms) between 2 quotas sync\n timeout = ${otoroshi.cluster.worker.timeout} # timeout when calling leader cluster on quotas sync\n }\n }\n }\n}\n```\n\nyou can also use many env. variables to configure Otoroshi cluster\n\n```hocon\notoroshi {\n cluster {\n mode = ${?CLUSTER_MODE}\n compression = ${?CLUSTER_COMPRESSION}\n leader {\n name = ${?CLUSTER_LEADER_NAME}\n host = ${?CLUSTER_LEADER_HOST}\n url = ${?CLUSTER_LEADER_URL}\n clientId = ${?CLUSTER_LEADER_CLIENT_ID}\n clientSecret = ${?CLUSTER_LEADER_CLIENT_SECRET}\n groupingBy = ${?CLUSTER_LEADER_GROUP_BY}\n cacheStateFor = ${?CLUSTER_LEADER_CACHE_STATE_FOR}\n stateDumpPath = ${?CLUSTER_LEADER_DUMP_PATH}\n }\n worker {\n name = ${?CLUSTER_WORKER_NAME}\n retries = ${?CLUSTER_WORKER_RETRIES}\n timeout = ${?CLUSTER_WORKER_TIMEOUT}\n state {\n retries = ${?CLUSTER_WORKER_STATE_RETRIES}\n pollEvery = ${?CLUSTER_WORKER_POLL_EVERY}\n timeout = ${?CLUSTER_WORKER_POLL_TIMEOUT}\n }\n quotas {\n retries = ${?CLUSTER_WORKER_QUOTAS_RETRIES}\n pushEvery = ${?CLUSTER_WORKER_PUSH_EVERY}\n timeout = ${?CLUSTER_WORKER_PUSH_TIMEOUT}\n }\n }\n }\n}\n```\n\n@@@ warning\nYou **should** use HTTPS exposition for the Otoroshi API that will be used for data sync as sensitive informations are exchanged between control plane and data plane.\n@@@\n\n@@@ warning\nYou **must** have the same cluster configuration on every Otoroshi instance (worker/leader) with only names and mode changed for each instance. Some things in leader/worker are computed using configuration of their counterpart worker/leader.\n@@@\n\n## Cluster UI\n\nOnce an Otoroshi instance is launcher as cluster Leader, a new row of live metrics tile will be available on the home page of Otoroshi admin UI.\n\n@@@ div { .centered-img }\n\n@@@\n\nyou can also access a more detailed view of the cluster at `Settings (cog icon) / Cluster View`\n\n@@@ div { .centered-img }\n\n@@@\n\n## Run examples\n\nfor leader \n\n```sh\njava -Dhttp.port=8091 -Dhttps.port=9091 -Dotoroshi.cluster.mode=leader -jar otoroshi.jar\n```\n\nfor worker\n\n```sh\njava -Dhttp.port=8092 -Dhttps.port=9092 -Dotoroshi.cluster.mode=worker \\\n -Dotoroshi.cluster.leader.urls.0=http://127.0.0.1:8091 -jar otoroshi.jar\n```\n" + }, + { + "name": "index.md", + "id": "/topics/index.md", + "url": "/topics/index.html", + "title": "Detailed topics", + "content": "# Detailed topics\n\nIn this sections, you will find informations about various topics supported by Otoroshi\n\n@@@ index\n\n* [Chaos engineering with the Snow Monkey](./snow-monkey.md)\n* [Service mesh](./service-mesh.md)\n* [JWT Tokens verification](./jwt-verifications.md)\n* [SSL/TLS termination with Otoroshi](./ssl.md)\n* [Mutual TLS with Otoroshi](./mtls.md)\n* [Otoroshi Clustering](./clustering.md)\n* [Requests transformation](./req-transformers.md)\n* [Otoroshi monitoring](./monitoring.md)\n\n@@@\n" + }, + { + "name": "jwt-verifications.md", + "id": "/topics/jwt-verifications.md", + "url": "/topics/jwt-verifications.html", + "title": "JWT Tokens verification", + "content": "# JWT Tokens verification\n\nSometimes, it can be pretty useful to verify Jwt tokens coming from other provider on some services. Otoroshi provides a tool to do that per service. In the Service descriptor page, you can find a `Jwt token Verification` section dedicated to this topic.\n\n## Service descriptor local verifications\n\n@@@ div { .centered-img }\n\n@@@\n\nin this section you can select the type of verification you can choose if the verifier is local to the `Service descriptor` or reference a global one.\n\nYou can also enabled/disable jwt verification and activate strict mode. In strict mode, requests will be rejected if the jwt token is not found.\n\n### Jwt token location\n\nYou can use the `Source` selector to specify where the Jwt token can be found. \n\n* in a query string param\n\n@@@ div { .centered-img }\n\n@@@\n\n* in a header\n\n@@@ div { .centered-img }\n\n@@@\n\n* in a cookie\n\n@@@ div { .centered-img }\n\n@@@\n\n### Jwt signing\n\nYou can use the `Algo.` selector to specify the signing algorithm to use to verifiy the token\n\n@@@ div { .centered-img }\n\n@@@\n\nyou can choose between\n\n* Hmac + SHA256\n* Hmac + SHA384\n* Hmac + SHA512\n* RSA + SHA256\n* RSA + SHA384\n* RSA + SHA512\n* Elliptic Curve + SHA256\n* Elliptic Curve + SHA384\n* Elliptic Curve + SHA512\n\n@@@ div { .centered-img }\n\n@@@\n\nYou can use syntax like `${env.MY_ENV_VAR}` or `${config.my.config.path}` to provide secret/keys values. \n\n\n### Just verify signature and fields value\n\nUsing the `Verif. strategy` selector, you can choose `Verify jwt token`. This will verify if the token is signed using the settings from `jwt signing` section and the value of the fields provided in `Verify token fields`. Then the token will be send to the target just like that.\n\n@@@ div { .centered-img }\n\n@@@\n\n### Re-sign the token\n\nUsing the `Verif. strategy` selector, you can choose `Verify and re-sign jwt token`. This will verify if the token is signed using the settings from `jwt signing` section and the value of the fields provided in `Verify token fields`. Then the token will be re-signed using the settings provided in `Re-sign algo` and will be send to the target.\n\n@@@ div { .centered-img }\n\n@@@\n\n### Transform the token\n\nUsing the `Verif. strategy` selector, you can choose `Verify, re-sign and transform jwt token`. This will verify if the token is signed using the settings from `jwt signing` section and the value of the fields provided in `Verify token fields`. Then the token will be re-signed using the settings provided in `Re-sign algo`. You can also change the location of the token using `Token location`, remove fields using `Remove token fields`, set fields value using `Set token fields` and even rename fields using `Rename token fields`.\n\n@@@ div { .centered-img }\n\n@@@\n\nYou can also use a mini expression language in `Set token fields`. You just have to add expressions in values like `${expression}`. Supported expressions are the following :\n\n* `${date}` => set the current date\n* `${date.format('dd/MM/yyyy')}` => set the current date formatted with the format you want\n* `${token.fieldName}` => get the value of the field named `fieldName`\n* `${token.fieldName.replace('a', 'b')}` => get the value of the field named `fieldName` and replace `a` with `b`\n* `${token.fieldName.replaceAll('[0-9]', '-')}` => get the value of the field named `fieldName` and replace digits with `-`\n\nyou can of course use multiple expressions in one field like `my-value-is-${date}-with${token.user}`\n\n## Global verifications\n\nYou can create global jwt verifiers and reference them in your services (from the `Type` selector). When you set the type of verification to `Reference to a global definition`, you can choose an existing global jwt verifier\n\n@@@ div { .centered-img }\n\n@@@\n\nTo create a global verifier, go to `Settings (cog icon) / Global Jwt Verifiers` and it will display the list of global verifiers.\n\n@@@ div { .centered-img }\n\n@@@\n\nyou can them create, edit or delete verifiers\n\n@@@ div { .centered-img }\n\n@@@\n\n" + }, + { + "name": "monitoring.md", + "id": "/topics/monitoring.md", + "url": "/topics/monitoring.html", + "title": "Monitoring Otoroshi", + "content": "# Monitoring Otoroshi\n\nThe Otoroshi API exposes two endpoints for \n\n* `/health`: the health of the Otoroshi instance\n* `/metrics`: the metrics of the Otoroshi instance, either in JSON or Prometheus format using the `Accept` header (with `application/json` / `application/prometheus` values) or the `format` query param (with `json` or `prometheus` values)\n\n## Endpoints security\n\nThe two endpoints are exposed publicly on the Otoroshi admin api. But you can remove the corresponding public pattern and query the endpoints using standard apikeys. If you don't want to use apikeys but don't want to expose the endpoints publicly, you can defined two config. variables (`app.health.accessKey` or `HEALTH_ACCESS_KEY` and `otoroshi.metrics.accessKey` or `OTOROSHI_METRICS_ACCESS_KEY`) that will hold an access key for the endpoints. Then you can call the endpoints with an `access_key` query param with the value defined in the config. If you don't defined `otoroshi.metrics.accessKey` but define `app.health.accessKey`, `otoroshi.metrics.accessKey` will have the value of `app.health.accessKey`.\n \n## Examples\n\nlet say `app.health.accessKey` has value `MILpkVv6f2kG9Xmnc4mFIYRU4rTxHVGkxvB0hkQLZwEaZgE2hgbOXiRsN1DBnbtY`\n\n```sh\n$ curl http://otoroshi-api.oto.tools:8080/health\\?access_key\\=MILpkVv6f2kG9Xmnc4mFIYRU4rTxHVGkxvB0hkQLZwEaZgE2hgbOXiRsN1DBnbtY\n{\"otoroshi\":\"healthy\",\"datastore\":\"healthy\"}\n\n$ curl -H 'Accept: application/json' http://otoroshi-api.oto.tools:8080/metrics\\?access_key\\=MILpkVv6f2kG9Xmnc4mFIYRU4rTxHVGkxvB0hkQLZwEaZgE2hgbOXiRsN1DBnbtY\n{\"version\":\"4.0.0\",\"gauges\":{\"attr.app.commit\":{\"value\":\"xxxx\"},\"attr.app.id\":{\"value\":\"xxxx\"},\"attr.cluster.mode\":{\"value\":\"Leader\"},\"attr.cluster.name\":{\"value\":\"otoroshi-leader-0\"},\"attr.instance.env\":{\"value\":\"prod\"},\"attr.instance.id\":{\"value\":\"xxxx\"},\"attr.instance.number\":{\"value\":\"0\"},\"attr.jvm.cpu.usage\":{\"value\":136},\"attr.jvm.heap.size\":{\"value\":1409},\"attr.jvm.heap.used\":{\"value\":112},\"internals.0.concurrent-requests\":{\"value\":1},\"internals.global.throttling-quotas\":{\"value\":2},\"jvm.attr.name\":{\"value\":\"2085@xxxx\"},\"jvm.attr.uptime\":{\"value\":2296900},\"jvm.attr.vendor\":{\"value\":\"JDK11\"},\"jvm.gc.PS-MarkSweep.count\":{\"value\":3},\"jvm.gc.PS-MarkSweep.time\":{\"value\":261},\"jvm.gc.PS-Scavenge.count\":{\"value\":12},\"jvm.gc.PS-Scavenge.time\":{\"value\":161},\"jvm.memory.heap.committed\":{\"value\":1477967872},\"jvm.memory.heap.init\":{\"value\":1690304512},\"jvm.memory.heap.max\":{\"value\":3005218816},\"jvm.memory.heap.usage\":{\"value\":0.03916456777568639},\"jvm.memory.heap.used\":{\"value\":117698096},\"jvm.memory.non-heap.committed\":{\"value\":166445056},\"jvm.memory.non-heap.init\":{\"value\":7667712},\"jvm.memory.non-heap.max\":{\"value\":994050048},\"jvm.memory.non-heap.usage\":{\"value\":0.1523920694986979},\"jvm.memory.non-heap.used\":{\"value\":151485344},\"jvm.memory.pools.CodeHeap-'non-nmethods'.committed\":{\"value\":2555904},\"jvm.memory.pools.CodeHeap-'non-nmethods'.init\":{\"value\":2555904},\"jvm.memory.pools.CodeHeap-'non-nmethods'.max\":{\"value\":5832704},\"jvm.memory.pools.CodeHeap-'non-nmethods'.usage\":{\"value\":0.28408093398876405},\"jvm.memory.pools.CodeHeap-'non-nmethods'.used\":{\"value\":1656960},\"jvm.memory.pools.CodeHeap-'non-profiled-nmethods'.committed\":{\"value\":11796480},\"jvm.memory.pools.CodeHeap-'non-profiled-nmethods'.init\":{\"value\":2555904},\"jvm.memory.pools.CodeHeap-'non-profiled-nmethods'.max\":{\"value\":122912768},\"jvm.memory.pools.CodeHeap-'non-profiled-nmethods'.usage\":{\"value\":0.09536102872567315},\"jvm.memory.pools.CodeHeap-'non-profiled-nmethods'.used\":{\"value\":11721088},\"jvm.memory.pools.CodeHeap-'profiled-nmethods'.committed\":{\"value\":37355520},\"jvm.memory.pools.CodeHeap-'profiled-nmethods'.init\":{\"value\":2555904},\"jvm.memory.pools.CodeHeap-'profiled-nmethods'.max\":{\"value\":122912768},\"jvm.memory.pools.CodeHeap-'profiled-nmethods'.usage\":{\"value\":0.2538573047187417},\"jvm.memory.pools.CodeHeap-'profiled-nmethods'.used\":{\"value\":31202304},\"jvm.memory.pools.Compressed-Class-Space.committed\":{\"value\":14942208},\"jvm.memory.pools.Compressed-Class-Space.init\":{\"value\":0},\"jvm.memory.pools.Compressed-Class-Space.max\":{\"value\":367001600},\"jvm.memory.pools.Compressed-Class-Space.usage\":{\"value\":0.033858838762555805},\"jvm.memory.pools.Compressed-Class-Space.used\":{\"value\":12426248},\"jvm.memory.pools.Metaspace.committed\":{\"value\":99794944},\"jvm.memory.pools.Metaspace.init\":{\"value\":0},\"jvm.memory.pools.Metaspace.max\":{\"value\":375390208},\"jvm.memory.pools.Metaspace.usage\":{\"value\":0.25168142904782426},\"jvm.memory.pools.Metaspace.used\":{\"value\":94478744},\"jvm.memory.pools.PS-Eden-Space.committed\":{\"value\":349700096},\"jvm.memory.pools.PS-Eden-Space.init\":{\"value\":422576128},\"jvm.memory.pools.PS-Eden-Space.max\":{\"value\":1110966272},\"jvm.memory.pools.PS-Eden-Space.usage\":{\"value\":0.07505125052077188},\"jvm.memory.pools.PS-Eden-Space.used\":{\"value\":83379408},\"jvm.memory.pools.PS-Eden-Space.used-after-gc\":{\"value\":0},\"jvm.memory.pools.PS-Old-Gen.committed\":{\"value\":1127219200},\"jvm.memory.pools.PS-Old-Gen.init\":{\"value\":1127219200},\"jvm.memory.pools.PS-Old-Gen.max\":{\"value\":2253914112},\"jvm.memory.pools.PS-Old-Gen.usage\":{\"value\":0.014950035505168354},\"jvm.memory.pools.PS-Old-Gen.used\":{\"value\":33696096},\"jvm.memory.pools.PS-Old-Gen.used-after-gc\":{\"value\":23791152},\"jvm.memory.pools.PS-Survivor-Space.committed\":{\"value\":1048576},\"jvm.memory.pools.PS-Survivor-Space.init\":{\"value\":70254592},\"jvm.memory.pools.PS-Survivor-Space.max\":{\"value\":1048576},\"jvm.memory.pools.PS-Survivor-Space.usage\":{\"value\":0.59375},\"jvm.memory.pools.PS-Survivor-Space.used\":{\"value\":622592},\"jvm.memory.pools.PS-Survivor-Space.used-after-gc\":{\"value\":622592},\"jvm.memory.total.committed\":{\"value\":1644412928},\"jvm.memory.total.init\":{\"value\":1697972224},\"jvm.memory.total.max\":{\"value\":3999268864},\"jvm.memory.total.used\":{\"value\":269184904},\"jvm.thread.blocked.count\":{\"value\":0},\"jvm.thread.count\":{\"value\":82},\"jvm.thread.daemon.count\":{\"value\":11},\"jvm.thread.deadlock.count\":{\"value\":0},\"jvm.thread.deadlocks\":{\"value\":[]},\"jvm.thread.new.count\":{\"value\":0},\"jvm.thread.runnable.count\":{\"value\":25},\"jvm.thread.terminated.count\":{\"value\":0},\"jvm.thread.timed_waiting.count\":{\"value\":10},\"jvm.thread.waiting.count\":{\"value\":47}},\"counters\":{},\"histograms\":{},\"meters\":{},\"timers\":{}}\n\n$ curl -H 'Accept: application/prometheus' http://otoroshi-api.oto.tools:8080/metrics\\?access_key\\=MILpkVv6f2kG9Xmnc4mFIYRU4rTxHVGkxvB0hkQLZwEaZgE2hgbOXiRsN1DBnbtY\n# TYPE attr_jvm_cpu_usage gauge\nattr_jvm_cpu_usage 83.0\n# TYPE attr_jvm_heap_size gauge\nattr_jvm_heap_size 1409.0\n# TYPE attr_jvm_heap_used gauge\nattr_jvm_heap_used 220.0\n# TYPE internals_0_concurrent_requests gauge\ninternals_0_concurrent_requests 1.0\n# TYPE internals_global_throttling_quotas gauge\ninternals_global_throttling_quotas 3.0\n# TYPE jvm_attr_uptime gauge\njvm_attr_uptime 2372614.0\n# TYPE jvm_gc_PS_MarkSweep_count gauge\njvm_gc_PS_MarkSweep_count 3.0\n# TYPE jvm_gc_PS_MarkSweep_time gauge\njvm_gc_PS_MarkSweep_time 261.0\n# TYPE jvm_gc_PS_Scavenge_count gauge\njvm_gc_PS_Scavenge_count 12.0\n# TYPE jvm_gc_PS_Scavenge_time gauge\njvm_gc_PS_Scavenge_time 161.0\n# TYPE jvm_memory_heap_committed gauge\njvm_memory_heap_committed 1.477967872E9\n# TYPE jvm_memory_heap_init gauge\njvm_memory_heap_init 1.690304512E9\n# TYPE jvm_memory_heap_max gauge\njvm_memory_heap_max 3.005218816E9\n# TYPE jvm_memory_heap_usage gauge\njvm_memory_heap_usage 0.07680553268571043\n# TYPE jvm_memory_heap_used gauge\njvm_memory_heap_used 2.30817432E8\n# TYPE jvm_memory_non_heap_committed gauge\njvm_memory_non_heap_committed 1.66510592E8\n# TYPE jvm_memory_non_heap_init gauge\njvm_memory_non_heap_init 7667712.0\n# TYPE jvm_memory_non_heap_max gauge\njvm_memory_non_heap_max 9.94050048E8\n# TYPE jvm_memory_non_heap_usage gauge\njvm_memory_non_heap_usage 0.15262878997416435\n# TYPE jvm_memory_non_heap_used gauge\njvm_memory_non_heap_used 1.51720656E8\n# TYPE jvm_memory_pools_CodeHeap__non_nmethods__committed gauge\njvm_memory_pools_CodeHeap__non_nmethods__committed 2555904.0\n# TYPE jvm_memory_pools_CodeHeap__non_nmethods__init gauge\njvm_memory_pools_CodeHeap__non_nmethods__init 2555904.0\n# TYPE jvm_memory_pools_CodeHeap__non_nmethods__max gauge\njvm_memory_pools_CodeHeap__non_nmethods__max 5832704.0\n# TYPE jvm_memory_pools_CodeHeap__non_nmethods__usage gauge\njvm_memory_pools_CodeHeap__non_nmethods__usage 0.28408093398876405\n# TYPE jvm_memory_pools_CodeHeap__non_nmethods__used gauge\njvm_memory_pools_CodeHeap__non_nmethods__used 1656960.0\n# TYPE jvm_memory_pools_CodeHeap__non_profiled_nmethods__committed gauge\njvm_memory_pools_CodeHeap__non_profiled_nmethods__committed 1.1862016E7\n# TYPE jvm_memory_pools_CodeHeap__non_profiled_nmethods__init gauge\njvm_memory_pools_CodeHeap__non_profiled_nmethods__init 2555904.0\n# TYPE jvm_memory_pools_CodeHeap__non_profiled_nmethods__max gauge\njvm_memory_pools_CodeHeap__non_profiled_nmethods__max 1.22912768E8\n# TYPE jvm_memory_pools_CodeHeap__non_profiled_nmethods__usage gauge\njvm_memory_pools_CodeHeap__non_profiled_nmethods__usage 0.09610562183417755\n# TYPE jvm_memory_pools_CodeHeap__non_profiled_nmethods__used gauge\njvm_memory_pools_CodeHeap__non_profiled_nmethods__used 1.1812608E7\n# TYPE jvm_memory_pools_CodeHeap__profiled_nmethods__committed gauge\njvm_memory_pools_CodeHeap__profiled_nmethods__committed 3.735552E7\n# TYPE jvm_memory_pools_CodeHeap__profiled_nmethods__init gauge\njvm_memory_pools_CodeHeap__profiled_nmethods__init 2555904.0\n# TYPE jvm_memory_pools_CodeHeap__profiled_nmethods__max gauge\njvm_memory_pools_CodeHeap__profiled_nmethods__max 1.22912768E8\n# TYPE jvm_memory_pools_CodeHeap__profiled_nmethods__usage gauge\njvm_memory_pools_CodeHeap__profiled_nmethods__usage 0.25493618368435084\n# TYPE jvm_memory_pools_CodeHeap__profiled_nmethods__used gauge\njvm_memory_pools_CodeHeap__profiled_nmethods__used 3.1334912E7\n# TYPE jvm_memory_pools_Compressed_Class_Space_committed gauge\njvm_memory_pools_Compressed_Class_Space_committed 1.4942208E7\n# TYPE jvm_memory_pools_Compressed_Class_Space_init gauge\njvm_memory_pools_Compressed_Class_Space_init 0.0\n# TYPE jvm_memory_pools_Compressed_Class_Space_max gauge\njvm_memory_pools_Compressed_Class_Space_max 3.670016E8\n# TYPE jvm_memory_pools_Compressed_Class_Space_usage gauge\njvm_memory_pools_Compressed_Class_Space_usage 0.03386023385184152\n# TYPE jvm_memory_pools_Compressed_Class_Space_used gauge\njvm_memory_pools_Compressed_Class_Space_used 1.242676E7\n# TYPE jvm_memory_pools_Metaspace_committed gauge\njvm_memory_pools_Metaspace_committed 9.9794944E7\n# TYPE jvm_memory_pools_Metaspace_init gauge\njvm_memory_pools_Metaspace_init 0.0\n# TYPE jvm_memory_pools_Metaspace_max gauge\njvm_memory_pools_Metaspace_max 3.75390208E8\n# TYPE jvm_memory_pools_Metaspace_usage gauge\njvm_memory_pools_Metaspace_usage 0.25170985813247426\n# TYPE jvm_memory_pools_Metaspace_used gauge\njvm_memory_pools_Metaspace_used 9.4489416E7\n# TYPE jvm_memory_pools_PS_Eden_Space_committed gauge\njvm_memory_pools_PS_Eden_Space_committed 3.49700096E8\n# TYPE jvm_memory_pools_PS_Eden_Space_init gauge\njvm_memory_pools_PS_Eden_Space_init 4.22576128E8\n# TYPE jvm_memory_pools_PS_Eden_Space_max gauge\njvm_memory_pools_PS_Eden_Space_max 1.110966272E9\n# TYPE jvm_memory_pools_PS_Eden_Space_usage gauge\njvm_memory_pools_PS_Eden_Space_usage 0.17698545577448457\n# TYPE jvm_memory_pools_PS_Eden_Space_used gauge\njvm_memory_pools_PS_Eden_Space_used 1.96624872E8\n# TYPE jvm_memory_pools_PS_Eden_Space_used_after_gc gauge\njvm_memory_pools_PS_Eden_Space_used_after_gc 0.0\n# TYPE jvm_memory_pools_PS_Old_Gen_committed gauge\njvm_memory_pools_PS_Old_Gen_committed 1.1272192E9\n# TYPE jvm_memory_pools_PS_Old_Gen_init gauge\njvm_memory_pools_PS_Old_Gen_init 1.1272192E9\n# TYPE jvm_memory_pools_PS_Old_Gen_max gauge\njvm_memory_pools_PS_Old_Gen_max 2.253914112E9\n# TYPE jvm_memory_pools_PS_Old_Gen_usage gauge\njvm_memory_pools_PS_Old_Gen_usage 0.014950035505168354\n# TYPE jvm_memory_pools_PS_Old_Gen_used gauge\njvm_memory_pools_PS_Old_Gen_used 3.3696096E7\n# TYPE jvm_memory_pools_PS_Old_Gen_used_after_gc gauge\njvm_memory_pools_PS_Old_Gen_used_after_gc 2.3791152E7\n# TYPE jvm_memory_pools_PS_Survivor_Space_committed gauge\njvm_memory_pools_PS_Survivor_Space_committed 1048576.0\n# TYPE jvm_memory_pools_PS_Survivor_Space_init gauge\njvm_memory_pools_PS_Survivor_Space_init 7.0254592E7\n# TYPE jvm_memory_pools_PS_Survivor_Space_max gauge\njvm_memory_pools_PS_Survivor_Space_max 1048576.0\n# TYPE jvm_memory_pools_PS_Survivor_Space_usage gauge\njvm_memory_pools_PS_Survivor_Space_usage 0.59375\n# TYPE jvm_memory_pools_PS_Survivor_Space_used gauge\njvm_memory_pools_PS_Survivor_Space_used 622592.0\n# TYPE jvm_memory_pools_PS_Survivor_Space_used_after_gc gauge\njvm_memory_pools_PS_Survivor_Space_used_after_gc 622592.0\n# TYPE jvm_memory_total_committed gauge\njvm_memory_total_committed 1.644478464E9\n# TYPE jvm_memory_total_init gauge\njvm_memory_total_init 1.697972224E9\n# TYPE jvm_memory_total_max gauge\njvm_memory_total_max 3.999268864E9\n# TYPE jvm_memory_total_used gauge\njvm_memory_total_used 3.82665128E8\n# TYPE jvm_thread_blocked_count gauge\njvm_thread_blocked_count 0.0\n# TYPE jvm_thread_count gauge\njvm_thread_count 82.0\n# TYPE jvm_thread_daemon_count gauge\njvm_thread_daemon_count 11.0\n# TYPE jvm_thread_deadlock_count gauge\njvm_thread_deadlock_count 0.0\n# TYPE jvm_thread_new_count gauge\njvm_thread_new_count 0.0\n# TYPE jvm_thread_runnable_count gauge\njvm_thread_runnable_count 25.0\n# TYPE jvm_thread_terminated_count gauge\njvm_thread_terminated_count 0.0\n# TYPE jvm_thread_timed_waiting_count gauge\njvm_thread_timed_waiting_count 10.0\n# TYPE jvm_thread_waiting_count gauge\njvm_thread_waiting_count 47.0\n```" + }, + { + "name": "mtls.md", + "id": "/topics/mtls.md", + "url": "/topics/mtls.html", + "title": "Mutual TLS with Otoroshi", + "content": "# Mutual TLS with Otoroshi\n\nOtoroshi support mutual TLS out of the box. mTLS from client to Otoroshi and from Otoroshi to targets are supported. In this article we will see how to configure Otoroshi to use end-to-end mTLS. All code and files used in this articles can be found on the [Otoroshi github](https://github.com/MAIF/otoroshi/tree/master/demos/mtls)\n\n@@@ note { title=\"Experimental Feature\" }\nDynamic Mutual TLS is an experimental feature. It can change until it becomess an official feature\n@@@\n\n## End-to-end mTLS\n\nThe use case is the following :\n\n@@@ div { .centered-img }\n\n@@@\n\nfor this demo you will have to edit your `/etc/hosts` file to add the following entries\n\n```\n127.0.0.1 api.backend.lol api.frontend.lol www.backend.lol www.frontend.lol validation.backend.lol\n```\n\n### Create certificates\n\nBut first we need to generate some certificates to make the demo work\n\n```sh\nmkdir mtls-demo\ncd mtls-demo\nmkdir ca\nmkdir server\nmkdir client\n\n# create a certificate authority key, use password as pass phrase\nopenssl genrsa -out ./ca/ca-backend.key 4096\n# remove pass phrase\nopenssl rsa -in ./ca/ca-backend.key -out ./ca/ca-backend.key\n# generate the certificate authority cert\nopenssl req -new -x509 -sha256 -days 730 -key ./ca/ca-backend.key -out ./ca/ca-backend.cer -subj \"/CN=MTLSB\"\n\n\n# create a certificate authority key, use password as pass phrase\nopenssl genrsa -out ./ca/ca-frontend.key 2048\n# remove pass phrase\nopenssl rsa -in ./ca/ca-frontend.key -out ./ca/ca-frontend.key\n# generate the certificate authority cert\nopenssl req -new -x509 -sha256 -days 730 -key ./ca/ca-frontend.key -out ./ca/ca-frontend.cer -subj \"/CN=MTLSF\"\n\n\n# now create the backend cert key, use password as pass phrase\nopenssl genrsa -out ./server/_.backend.lol.key 2048\n# remove pass phrase\nopenssl rsa -in ./server/_.backend.lol.key -out ./server/_.backend.lol.key\n# generate the csr for the certificate\nopenssl req -new -key ./server/_.backend.lol.key -sha256 -out ./server/_.backend.lol.csr -subj \"/CN=*.backend.lol\"\n# generate the certificate\nopenssl x509 -req -days 365 -sha256 -in ./server/_.backend.lol.csr -CA ./ca/ca-backend.cer -CAkey ./ca/ca-backend.key -set_serial 1 -out ./server/_.backend.lol.cer\n# verify the certificate, should output './server/_.backend.lol.cer: OK'\nopenssl verify -CAfile ./ca/ca-backend.cer ./server/_.backend.lol.cer\n\n\n# now create the frontend cert key, use password as pass phrase\nopenssl genrsa -out ./server/_.frontend.lol.key 2048\n# remove pass phrase\nopenssl rsa -in ./server/_.frontend.lol.key -out ./server/_.frontend.lol.key\n# generate the csr for the certificate\nopenssl req -new -key ./server/_.frontend.lol.key -sha256 -out ./server/_.frontend.lol.csr -subj \"/CN=*.frontend.lol\"\n# generate the certificate\nopenssl x509 -req -days 365 -sha256 -in ./server/_.frontend.lol.csr -CA ./ca/ca-frontend.cer -CAkey ./ca/ca-frontend.key -set_serial 1 -out ./server/_.frontend.lol.cer\n# verify the certificate, should output './server/_.frontend.lol.cer: OK'\nopenssl verify -CAfile ./ca/ca-frontend.cer ./server/_.frontend.lol.cer\n\n\n# now create the client cert key for backend, use password as pass phrase\nopenssl genrsa -out ./client/_.backend.lol.key 2048\n# remove pass phrase\nopenssl rsa -in ./client/_.backend.lol.key -out ./client/_.backend.lol.key\n# generate the csr for the certificate\nopenssl req -new -key ./client/_.backend.lol.key -out ./client/_.backend.lol.csr -subj \"/CN=*.backend.lol\"\n# generate the certificate\nopenssl x509 -req -days 365 -sha256 -in ./client/_.backend.lol.csr -CA ./ca/ca-backend.cer -CAkey ./ca/ca-backend.key -set_serial 2 -out ./client/_.backend.lol.cer\n# generate a pkcs12 version of the cert and key, use password as password\nopenssl pkcs12 -export -clcerts -in client/_.backend.lol.cer -inkey client/_.backend.lol.key -out client/_.backend.lol.p12\n\n\n# now create the client cert key for frontend, use password as pass phrase\nopenssl genrsa -out ./client/_.frontend.lol.key 2048\n# remove pass phrase\nopenssl rsa -in ./client/_.frontend.lol.key -out ./client/_.frontend.lol.key\n# generate the csr for the certificate\nopenssl req -new -key ./client/_.frontend.lol.key -out ./client/_.frontend.lol.csr -subj \"/CN=*.frontend.lol\"\n# generate the certificate\nopenssl x509 -req -days 365 -sha256 -in ./client/_.frontend.lol.csr -CA ./ca/ca-frontend.cer -CAkey ./ca/ca-frontend.key -set_serial 2 -out ./client/_.frontend.lol.cer\n# generate a pkcs12 version of the cert and key, use password as password\nopenssl pkcs12 -export -clcerts -in client/_.frontend.lol.cer -inkey client/_.frontend.lol.key -out client/_.frontend.lol.p12\n```\n\nonce it's done, you should have something like\n\n```sh\n$ tree\n.\n├── backend.js\n├── ca\n│   ├── ca-backend.cer\n│   ├── ca-backend.key\n│   ├── ca-frontend.cer\n│   └── ca-frontend.key\n├── client\n│   ├── _.backend.lol.cer\n│   ├── _.backend.lol.csr\n│   ├── _.backend.lol.key\n│   ├── _.backend.lol.p12\n│   ├── _.frontend.lol.cer\n│   ├── _.frontend.lol.csr\n│   ├── _.frontend.lol.key\n│   └── _.frontend.lol.p12\n└── server\n ├── _.backend.lol.cer\n ├── _.backend.lol.csr\n ├── _.backend.lol.key\n ├── _.frontend.lol.cer\n ├── _.frontend.lol.csr\n └── _.frontend.lol.key\n\n3 directories, 18 files\n```\n\n### The backend service \n\nnow, let's create a backend service using nodejs. Create a file named `backend.js`\n\n```sh\ntouch backend.js\n```\n\nand put the following content\n\n```js\nconst fs = require('fs'); \nconst https = require('https'); \n\nconst options = { \n key: fs.readFileSync('./server/_.backend.lol.key'), \n cert: fs.readFileSync('./server/_.backend.lol.cer'), \n ca: fs.readFileSync('./ca/ca-backend.cer'), \n}; \n\nhttps.createServer(options, (req, res) => { \n res.writeHead(200, {\n 'Content-Type': 'application/json'\n }); \n res.end(JSON.stringify({ message: 'Hello World!' }) + \"\\n\"); \n}).listen(8444);\n```\n\nto run the server, just do \n\n```sh\nnode ./backend.js\n```\n\nnow you can try your server with\n\n```sh\ncurl --cacert ./ca/ca-backend.cer https://api.backend.lol:8444/\n# will print {\"message\":\"Hello World!\"}\n```\n\nnow modify your backend server to ensure that the client provides a client certificate like:\n\n```js\nconst fs = require('fs'); \nconst https = require('https'); \n\nconst options = { \n key: fs.readFileSync('./server/_.backend.lol.key'), \n cert: fs.readFileSync('./server/_.backend.lol.cer'), \n ca: fs.readFileSync('./ca/ca-backend.cer'), \n requestCert: true, \n rejectUnauthorized: true\n}; \n\nhttps.createServer(options, (req, res) => { \n console.log('Client certificate CN: ', req.socket.getPeerCertificate().subject.CN);\n res.writeHead(200, {\n 'Content-Type': 'application/json'\n }); \n res.end(JSON.stringify({ message: 'Hello World!' }) + \"\\n\"); \n}).listen(8444);\n```\n\nyou can test your new server with\n\n```sh\ncurl --cacert ./ca/ca-backend.cer --cert-type pkcs12 --cert ./client/_.backend.lol.p12:password https://api.backend.lol:8444/\n# will print {\"message\":\"Hello World!\"}\n```\n\n### Otoroshi setup\n\nDownload the latest version of the Otoroshi jar and run it like\n\n```sh\njava -jar otoroshi.jar\n\n[info] otoroshi-env - Admin API exposed on http://otoroshi-api.oto.tools:8080\n[info] otoroshi-env - Admin UI exposed on http://otoroshi.oto.tools:8080\n[info] otoroshi-in-memory-datastores - Now using InMemory DataStores\n[info] otoroshi-env - The main datastore seems to be empty, registering some basic services\n[info] otoroshi-env - You can log into the Otoroshi admin console with the following credentials: admin@otoroshi.io / xxxxxxxxxxxx\n[info] play.api.Play - Application started (Prod)\n[info] p.c.s.AkkaHttpServer - Listening for HTTP on /0:0:0:0:0:0:0:0:8080\n[info] p.c.s.AkkaHttpServer - Listening for HTTPS on /0:0:0:0:0:0:0:0:8443\n[info] otoroshi-env - Generating a self signed SSL certificate for https://*.oto.tools ...\n```\n\nand log into otoroshi with the tuple `admin@otoroshi.io / xxxxxxxxxxxx` displayed in the logs. Once logged in, create a new public service exposed on `http://api.frontend.lol` that targets `ahttps://api.backend.lol:8444/`.\n\n@@@ div { .centered-img }\n\n@@@\n\nand test it\n\n```sh\ncurl http://api.frontend.lol:8080/\n# the following error should be returned: {\"Otoroshi-Error\":\"Something went wrong, you should try later. Thanks for your understanding.\"}\n```\n\n@@@ warning\nAs seen before, the target of the otoroshi service is `ahttps://api.backend.lol:8444/`. `ahttps://` is not a typo and is intended. This tells otoroshi to use its experimental `http client` with dynamic tls support to fetch this resource.\n@@@\n\nyou should get an error due to the fact that Otoroshi doesn't know about the server certificate or the client certificate expected by the server.\n\nWe have to add the client certificate for `https://api.backend.lol` to Otoroshi. Go to http://otoroshi.oto.tools:8080/bo/dashboard/certificates and create a new item. Copy and paste the content of `./client/_.backend.lol.cer` and `./client/_.backend.lol.key` respectively in `Certificate full chain` and `Certificate private key`.\n\n@@@ div { .centered-img }\n\n@@@\n\nand retry the following curl command \n\n```sh\ncurl http://api.frontend.lol:8080/\n# the output should be: {\"message\":\"Hello World!\"}\n```\n\nnow we have to expose `https://api.frontend.lol:8443` using otoroshi. Go to http://otoroshi.oto.tools:8080/bo/dashboard/certificates and create a new item. Copy and paste the content of `./server/_.frontend.lol.cer` and `./server/_.frontend.lol.key` respectively in `Certificate full chain` and `Certificate private key`.\n\nand try the following command\n\n```sh\ncurl --cacert ./ca/ca-frontend.cer https://api.frontend.lol:8443/\n# the output should be: {\"message\":\"Hello World!\"}\n```\n\nnow we have to enforce the fact that we want client certificate for `api.frontend.lol`. To do that, we have to create a `Validation authority` in otoroshi and use it on the `api.frontend.lol` service. Go to http://otoroshi.oto.tools:8080/bo/dashboard/validation-authorities and create a new item. A validation authority is supposed to be a remote service that will say if the client certificate is valid. Here we don't really care if the certificate is valid or not, but we want to enforce the fact that there is a client certificate. So just check the `All cert. valid button`.\n\n@@@ div { .centered-img }\n\n@@@\n\nnow go back on your `api.frontend.lol` service, in the `Validation authority` section and select the authority you just created.\n\n@@@ div { .centered-img }\n\n@@@\n\nnow if you retry \n\n```sh\ncurl --cacert ./ca/ca-frontend.cer https://api.frontend.lol:8443/\n# the output should be: {\"Otoroshi-Error\":\"You're not authorized here !\"}\n```\n\nyou should get an error because no client cert. is passed with the request. But if you pass the `./client/_.frontend.lol.p12` client cert in your curl call\n\n```sh\ncurl --cacert ./ca/ca-frontend.cer --cert-type pkcs12 --cert ./client/_.frontend.lol.p12:password https://api.frontend.lol:8443/\n# the output should be: {\"message\":\"Hello World!\"}\n```\n\n### End to end test\n\nNow we can try to write a small nodejs client that uses our client certificates. Create a `client.js` file with the following code\n\n```js\nconst fs = require('fs'); \nconst https = require('https'); \n\nprocess.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0;\n\nconst options = { \n hostname: 'api.frontend.lol', \n port: 8443, \n path: '/', \n method: 'GET', \n key: fs.readFileSync('./client/_.frontend.lol.key'), \n cert: fs.readFileSync('./client/_.frontend.lol.cer'), \n ca: fs.readFileSync('./ca/ca-frontend.cer'), \n}; \n\nconst req = https.request(options, (res) => { \n console.log('statusCode', res.statusCode);\n console.log('headers', res.headers);\n console.log('body:');\n res.on('data', (data) => { \n process.stdout.write(data); \n }); \n}); \n\nreq.end(); \n\nreq.on('error', (e) => { \n console.error(e); \n});\n```\n\nand run the following command\n\n```sh\n$ node client.js\n# statusCode 200\n# headers { date: 'Mon, 10 Dec 2018 16:01:11 GMT',\n# connection: 'close',\n# 'transfer-encoding': 'chunked',\n# 'content-type': 'application/json' }\n# body:\n# {\"message\":\"Hello World!\"}\n```\n\nAnd that's it \n\n## Validating client certificates based on user identity\n\n@@@ note { title=\"Experimental Feature\" }\nValidation authorities is an experimental feature. It can change until it becomess an official feature\n@@@\n\nThe use case is the following :\n\n@@@ div { .centered-img }\n\n@@@\n\nthe idea here is to provide a unique client certificate per device that can access Otoroshi and use a validation authority to check if the user is allowed to access the underlying app with a specific device.\n\n### Generate client certificates for devices\n\nTo do that we are going to create two client certificates, one per device (let say for a laptop and a desktop computer). We are going to use the device serial number as common name of the certificate to be able to identify the device behind the certificate.\n\n```sh\nopenssl genrsa -out ./client/device-1.key 2048\nopenssl rsa -in ./client/device-1.key -out ./client/device-1.key\nopenssl req -new -key ./client/device-1.key -out ./client/device-1.csr -subj \"/CN=mbp-123456789\"\nopenssl x509 -req -days 365 -sha256 -in ./client/device-1.csr -CA ./ca/ca-frontend.cer -CAkey ./ca/ca-frontend.key -set_serial 3 -out ./client/device-1\nopenssl pkcs12 -export -clcerts -in client/device-1 -inkey client/device-1.key -out client/device-1.p12\n\nopenssl genrsa -out ./client/device-2.key 2048\nopenssl rsa -in ./client/device-2.key -out ./client/device-2.key\nopenssl req -new -key ./client/device-2.key -out ./client/device-2.csr -subj \"/CN=nuc-987654321\"\nopenssl x509 -req -days 365 -sha256 -in ./client/device-2.csr -CA ./ca/ca-frontend.cer -CAkey ./ca/ca-frontend.key -set_serial 4 -out ./client/device-2\nopenssl pkcs12 -export -clcerts -in client/device-2 -inkey client/device-2.key -out client/device-2.p12\n```\n\n### Setup actual validation\n\nnow we are going to write an validation authority (with mTLS too) that is going to respond on `https://validation.backend.lol:8445`. The server has access to a list of apps, users and devices to check if everything is correct. In this implementation, the lists are hardcoded, but you can write your own implementation that will fetch data from your corporate LDAP, CA, etc. Create a `validation.js` file and add the following content. Don't forget to do `yarn add x509` before running the server with `node validation.js`\n\n```js\nconst fs = require('fs'); \nconst https = require('https'); \nconst x509 = require('x509');\n\n// list of knwon apps\nconst apps = [\n {\n \"id\": \"iogOIDH09EktFhydTp8xspGvdaBq961DUDr6MBBNwHO2EiBMlOdafGnImhbRGy8z\",\n \"name\": \"my-web-service\",\n \"description\": \"A service that says hello\",\n \"host\": \"www.frontend.lol\"\n }\n];\n\n// list of known users\nconst users = [\n {\n \"name\": \"Mathieu\",\n \"email\": \"mathieu@oto.tools\",\n \"appRights\": [\n {\n \"id\": \"iogOIDH09EktFhydTp8xspGvdaBq961DUDr6MBBNwHO2EiBMlOdafGnImhbRGy8z\",\n \"profile\": \"user\",\n \"forbidden\": false\n },\n {\n \"id\": \"PqgOIDH09EktFhydTp8xspGvdaBq961DUDr6MBBNwHO2EiBMlOdafGnImhbRGy8z\",\n \"profile\": \"none\",\n \"forbidden\": true\n },\n ],\n \"ownedDevices\": [\n \"mbp-123456789\",\n \"nuc-987654321\",\n ]\n }\n];\n\n// list of known devices\nconst devices = [\n {\n \"serialNumber\": \"mbp-123456789\",\n \"hardware\": \"Macbook Pro 2018 13 inc. with TouchBar, 2.6 GHz, 16 Gb\",\n \"acquiredAt\": \"2018-10-01\",\n },\n {\n \"serialNumber\": \"nuc-987654321\",\n \"hardware\": \"Intel NUC i7 3.0 GHz, 32 Gb\",\n \"acquiredAt\": \"2018-09-01\",\n },\n {\n \"serialNumber\": \"iphone-1234\",\n \"hardware\": \"Iphone XS, 256 Gb\",\n \"acquiredAt\": \"2018-12-01\",\n }\n];\n\nconst options = { \n key: fs.readFileSync('./server/_.backend.lol.key'), \n cert: fs.readFileSync('./server/_.backend.lol.cer'), \n ca: fs.readFileSync('./ca/ca-backend.cer'), \n requestCert: true, \n rejectUnauthorized: true\n}; \n\nfunction readBody(request) {\n return new Promise((success, failure) => {\n const body = [];\n request.on('data', (chunk) => {\n body.push(chunk);\n }).on('end', () => {\n const bodyStr = Buffer.concat(body).toString();\n success(JSON.parse(bodyStr));\n });\n });\n}\n\nfunction chainIsValid(chain) {\n // validate cert dates\n // validate cert against clr\n // validate whatever you want here\n return true;\n}\n\nfunction call(req, res) {\n readBody(req).then(body => {\n const service = body.service;\n const email = (body.user || { email: 'mathieu@oto.tools' }).email; // here, should not be null if used with an otoroshi auth. module\n // common name should be device serial number\n const commonName = x509.getSubject(body.chain).commonName\n // search for a known device\n const device = devices.filter(d => d.serialNumber === commonName)[0];\n // search for a known user\n const user = users.filter(d => d.email === email)[0];\n // search for a known application\n const app = apps.filter(d => d.id === service.id)[0];\n res.writeHead(200, { 'Content-Type': 'application/json' }); \n if (chainIsValid(body.chain.map(x509.parseCert)) && user && device && app) {\n // check if the user actually owns the device\n const userOwnsDevice = user.ownedDevices.filter(d => d === device.serialNumber)[0];\n // check if the user has rights to access the app\n const rights = user.appRights.filter(d => d.id === app.id)[0];\n const hasRightToUseApp = !rights.forbidden\n if (userOwnsDevice && hasRightToUseApp) {\n // yeah !!!!\n console.log(`Call from user \"${user.email}\" with device \"${device.hardware}\" on app \"${app.name}\" with profile \"${rights.profile}\" authorized`)\n res.end(JSON.stringify({ status: 'good', profile: rights.profile }) + \"\\n\"); \n } else {\n // nope !!! nope, nope nope\n console.log(`Call from user \"${user.email}\" with device \"${device.hardware}\" on app \"${app.name}\" unauthorized because user doesn't owns the hardware or has no rights`)\n res.end(JSON.stringify({ status: 'unauthorized' }) + \"\\n\"); \n }\n } else {\n console.log(`Call unauthorized`)\n res.end(JSON.stringify({ status: 'unauthorized' }) + \"\\n\"); \n }\n });\n}\n\nhttps.createServer(options, call).listen(8445);\n```\n\nthe corresponding authority validation can be created in Otoroshi like \n\n```json\n{\n \"id\": \"r7m8j31rh66hhdia3ormfm0wfevu1kvg0zgaxsp3oxb6ivf7fy8kvygmvnrlxv81\",\n \"name\": \"Actual validation authority\",\n \"description\": \"Actual validation authority\",\n \"url\": \"ahttps://validation.backend.lol:8445\",\n \"host\": \"validation.backend.lol\",\n \"goodTtl\": 600000,\n \"badTtl\": 60000,\n \"method\": \"POST\",\n \"path\": \"/certificates/_validate\",\n \"timeout\": 10000,\n \"noCache\": false,\n \"alwaysValid\": false,\n \"headers\": {}\n}\n```\n\nbut you don't need to create it right now.\n\nTypically, a validation authority server is a server with a route on `POST /certificates/_validate` that accepts `application/json` and returns `application/json` with a body like\n\n```json\n{\n \"apikey\": nullable {\n \"clientId\": String,\n \"clientName\": String,\n \"authorizedGroup\": String,\n \"enabled\": Boolean,\n \"readOnly\": Boolean,\n \"allowClientIdOnly\": Boolean,\n \"throttlingQuota\": Long,\n \"dailyQuota\": Long,\n \"monthlyQuota\": Long,\n \"metadata\": Map[String, String]\n },\n \"user\": nullable {\n \"email\": String,\n \"name\": String,\n },\n \"service\": {\n \"id\": String,\n \"name\": String,\n \"groupId\": String,\n \"domain\": String,\n \"env\": String,\n \"subdomain\": String,\n \"root\": String,\n \"metadata\": String\n },\n \"chain\": PemFormattedCertificateChainString,\n \"fingerprints\": Array[String]\n}\n```\n\n\n### Setup Otoroshi\n\nYou can start Otoroshi and import data from the `state.json` file in the demo folder. The login tuple is `admin@otoroshi.io / password`. The `state.json` file contains everything you need for the demo, like certificates, service descriptors, auth. modules, etc ...\n\n```sh\njava -Dapp.importFrom=$(pwd)/state.json -Dapp.privateapps.port=8080 -jar otoroshi.jar\n\n[info] otoroshi-env - Admin API exposed on http://otoroshi-api.oto.tools:8080\n[info] otoroshi-env - Admin UI exposed on http://otoroshi.oto.tools:8080\n[info] otoroshi-in-memory-datastores - Now using InMemory DataStores\n[info] otoroshi-env - The main datastore seems to be empty, registering some basic services\n[info] otoroshi-env - Importing from: /pwd/state.json\n[info] play.api.Play - Application started (Prod)\n[info] otoroshi-env - Successful import !\n[info] p.c.s.AkkaHttpServer - Listening for HTTP on /0:0:0:0:0:0:0:0:8080\n[info] p.c.s.AkkaHttpServer - Listening for HTTPS on /0:0:0:0:0:0:0:0:8443\n```\n\n### Testing \n\nYou can test the service with curl like\n\n```sh\ncurl --cacert ./ca/ca-frontend.cer --cert-type pkcs12 --cert ./client/device-1.p12:password https://www.frontend.lol:8443/\n# output:

Hello World !!!

\ncurl --cacert ./ca/ca-frontend.cer --cert-type pkcs12 --cert ./client/device-2.p12:password https://www.frontend.lol:8443/\n# output:

Hello World !!!

\ncurl --cacert ./ca/ca-frontend.cer --cert-type pkcs12 --cert ./client/_.frontend.lol.p12:password https://www.frontend.lol:8443/\n# output: {\"Otoroshi-Error\":\"You're not authorized here !\"}\n```\n\nas expected, the first two call works as their common name is known by the validation server. The last one fails as it's not known.\n\n### Validate user identity\n\nNow let's try to setup firefox to provide the client certificate. Open firefox settings, go to `privacy settings and security` and click on `display certificates` at the bottom of the page. Here you can add the frontend CA (`./ca/ca-frontend.cer`) in the `Authorities` tab, check the 'authorize this CA to identify websites', and then in the `certificates` tab, import one of the devices `.p12` file (like `./client/device-1.p12`). Firefox will ask for the files password (it should be `password`).\n\n@@@ div { .centered-img }\n\n@@@\n\nNow restart firefox.\n\nNext, go to the `my-web-service` service in otoroshi (log in with `admin@otoroshi.io / password`) and activate `Enforce user login` in the Authentication section. It means that now, you'll have to log in when you'll go to https://www.frontend.lol:8443. With authentication activated on otoroshi, the user identity will be sent to the validation authority, so you can change the following line in the file `validation.js`\n\n```js\nconst email = (body.user || { email: 'mathieu@oto.tools' }).email; // here, should not be null if used with an otoroshi auth. module\n```\n\nto\n\n```js\nconst email = body.user.email;\n```\n\nThen, in Firefox, go to https://www.frontend.lol:8443/, firefox will ask which client certificate to use. Select the one you imported (in the process, maybe firefox will warn you that the certificate of the site is auto signed, just ignore it and continue ;) )\n\n@@@ div { .centered-img }\n\n@@@\n\nthen, you'll see a login screen from otoroshi. You can log in with `mathieu@oto.tools / password` and then you should see the hello world message.\n\n@@@ div { .centered-img }\n\n@@@\n\n### Going further with user authentication\n\nFor stronger user authentication, you can try to use an auth. module baked by a keycloak instance with yubikey as a strong second factor authentication instead of the basic auth. module we used previously in this article.\n" + }, + { + "name": "req-transformers.md", + "id": "/topics/req-transformers.md", + "url": "/topics/req-transformers.html", + "title": "Request transformers", + "content": "# Request transformers\n\nWhen everything has failed and you absolutely need a feature in Otoroshi to make your use case work, there is a solution. Request transformer is an experimental feature hidden behind a feature flag that allow you to code how Otoroshi should behave when receiving and rounting an http request. With request transformers, you can change request / response headers and request / response body to way you want.\n\n@@@ note { title=\"Experimental Feature\" }\nRequest transformers is an experimental feature. It can change until it becomess an official feature\n@@@\n\n@@@ warning { title=\"Use at your own risk\" }\nRequest transformers is a **very** powerful feature that allow you to do almost everything you want. But like any powerful feature, it comes with a price. The fact that you are responsible for how the request is transformed makes you responsible for all issues introduced by the transformer. If you block the current thread, introduce big latency, generate uncatched exceptions, etc. it's **your fault**. You have to ensure that your code is fast, non blocking, safe, etc. In any case, Otoroshi developers will not responsible for issues caused by a request transformer.\n\nAnd always remember this quote from ~~uncle Ben~~ Winston Churchill:\n\n

\"Where there is great power there is great responsibility\"

\n@@@\n\n## Enabling request transformers\n\nFirst you have to enable Otoroshi request transformers with the `-Dotoroshi.scripts.enabled=true` flag, or using env. variable `OTOROSHI_SCRIPTS_ENABLED=true`.\n\n## Anatomy of a transformer\n\nA request transformer is a piece of Scala code than can handle the complete lifecycle of an http request made on Otoroshi\n\n```scala\ncase class HttpRequest(url: String, method: String, headers: Map[String, String], query: Map[String, String], cookies: Seq[WSCookie] = Seq.empty[WSCookie]) {\n lazy val host: String = headers.getOrElse(\"Host\", \"\")\n lazy val uri: Uri = Uri(url)\n lazy val scheme: String = uri.scheme\n lazy val authority: Uri.Authority = uri.authority\n lazy val fragment: Option[String] = uri.fragment\n lazy val path: String = uri.path.toString()\n lazy val queryString: Option[String] = uri.rawQueryString\n lazy val relativeUri: String = uri.toRelative.toString()\n}\ncase class HttpResponse(status: Int, headers: Map[String, String], cookies: Seq[WSCookie] = Seq.empty[WSCookie])\n\ntrait RequestTransformer {\n\n /**\n * See RequestTransformer.transformRequest\n */\n def transformRequestSync(\n snowflake: String, // a cluster unique request id\n rawRequest: HttpRequest, // the http request as received by Otoroshi\n otoroshiRequest: HttpRequest, // the http request modified by Otoroshi, ready to be sent to the target service\n desc: ServiceDescriptor, // the service description for the request \n apiKey: Option[ApiKey] = None, // the api key used, if one\n user: Option[PrivateAppsUser] = None // the user, if one\n )(implicit \n env: Env, // the otoroshi environnment\n ec: ExecutionContext, // the threadpool for the request to perform async actions\n mat: Materializer // the akka materializer to work with streams\n ): Either[Result, HttpRequest] = {\n Right(otoroshiRequest)\n }\n\n /**\n * Transform the request forwarded from Otoroshi to the target service\n */\n def transformRequest(\n snowflake: String, // a cluster unique request id\n rawRequest: HttpRequest, // the http request as received by Otoroshi\n otoroshiRequest: HttpRequest, // the http request modified by Otoroshi, ready to be sent to the target service\n desc: ServiceDescriptor, // the service description for the request \n apiKey: Option[ApiKey] = None, // the api key used, if one\n user: Option[PrivateAppsUser] = None // the user, if one\n )(implicit \n env: Env, // the otoroshi environnment\n ec: ExecutionContext, // the threadpool for the request to perform async actions\n mat: Materializer // the akka materializer to work with streams\n ): Future[Either[Result, HttpRequest]] = { // return a future of Either. On the left side, an http error if there was en err. On the Right side, the transformed http request\n FastFuture.successful(transformRequestSync(snowflake, rawRequest, otoroshiRequest, desc, apiKey, user)(env, ec, mat))\n }\n\n /**\n * See RequestTransformer.transformResponse\n */\n def transformResponseSync(\n snowflake: String, // a cluster unique request id\n rawResponse: HttpResponse, // the http response as received by Otoroshi from the target service\n otoroshiResponse: HttpResponse, // the http response modified by Otoroshi, ready to be sent back to the client\n desc: ServiceDescriptor, // the service description for the request \n apiKey: Option[ApiKey] = None, // the api key used, if one\n user: Option[PrivateAppsUser] = None // the user, if one\n )(implicit \n env: Env, // the otoroshi environnment\n ec: ExecutionContext, // the threadpool for the request to perform async actions\n mat: Materializer // the akka materializer to work with streams\n ): Either[Result, HttpResponse] = {\n Right(otoroshiResponse)\n }\n\n /**\n * Transform the response from the target to the client\n */ \n def transformResponse(\n snowflake: String, // a cluster unique request id\n rawResponse: HttpResponse, // the http response as received by Otoroshi from the target service\n otoroshiResponse: HttpResponse, // the http response modified by Otoroshi, ready to be sent back to the client\n desc: ServiceDescriptor, // the service description for the request \n apiKey: Option[ApiKey] = None, // the api key used, if one\n user: Option[PrivateAppsUser] = None // the user, if one\n )(implicit \n env: Env, // the otoroshi environnment\n ec: ExecutionContext, // the threadpool for the request to perform async actions\n mat: Materializer // the akka materializer to work with streams\n ): Future[Either[Result, HttpResponse]] = { // return a future of Either. On the left side, an http error if there was en err. On the Right side, the transformed http response\n FastFuture.successful(transformResponseSync(snowflake, rawResponse, otoroshiResponse, desc, apiKey, user)(env, ec, mat))\n }\n\n /**\n * Transform the request body forwarded from Otoroshi to the target service\n * It uses akka-stream (see https://doc.akka.io/docs/akka/2.5/index.html)\n */\n def transformRequestBody(\n snowflake: String, // a cluster unique request id\n body: Source[ByteString, _], // the body of the request, a stream of byte array\n rawRequest: HttpRequest, // the http request as received by Otoroshi\n otoroshiRequest: HttpRequest, // the http request modified by Otoroshi, ready to be sent to the target service\n desc: ServiceDescriptor, // the service description for the request \n apiKey: Option[ApiKey] = None, // the api key used, if one\n user: Option[PrivateAppsUser] = None // the user, if one\n )(implicit \n env: Env, // the otoroshi environnment\n ec: ExecutionContext, // the threadpool for the request to perform async actions\n mat: Materializer // the akka materializer to work with streams\n ): Source[ByteString, _] = { // return a stream of byte array\n body\n }\n\n /**\n * Transform the response body forwarded from target to the client. \n * It uses akka-stream (see https://doc.akka.io/docs/akka/2.5/index.html)\n */\n def transformResponseBody(\n snowflake: String, // a cluster unique request id\n body: Source[ByteString, _], // the body of the response, a stream of byte array\n rawResponse: HttpResponse, // the http response as received by Otoroshi from the target service\n otoroshiResponse: HttpResponse, // the http response modified by Otoroshi, ready to be sent back to the client\n desc: ServiceDescriptor, // the service description for the request \n apiKey: Option[ApiKey] = None, // the api key used, if one\n user: Option[PrivateAppsUser] = None // the user, if one\n )(implicit \n env: Env, // the otoroshi environnment\n ec: ExecutionContext, // the threadpool for the request to perform async actions\n mat: Materializer // the akka materializer to work with streams\n ): Source[ByteString, _] = {\n body\n }\n}\n```\n\nfor more information about APIs you can use\n\n* https://www.playframework.com/documentation/2.6.x/api/scala/index.html#package\n* https://www.playframework.com/documentation/2.6.x/api/scala/index.html#play.api.mvc.Results\n* https://github.com/MAIF/otoroshi\n* https://doc.akka.io/docs/akka/2.5/stream/index.html\n* https://doc.akka.io/api/akka/current/akka/stream/index.html\n* https://doc.akka.io/api/akka/current/akka/stream/scaladsl/Source.html\n\n## Writing a transformer from Otoroshi UI\n\nLog into Otoroshi and go to `Settings (cog icon) / Scripts`. Here you can create multiple request transformer scripts and associate it with service descriptor later.\n\n@@@ div { .centered-img }\n\n@@@\n\nwhen you write an instance of a transformer in the Otoroshi UI, do the following\n\n```scala\nimport akka.stream.Materializer\nimport env.Env\nimport models.{ApiKey, PrivateAppsUser, ServiceDescriptor}\nimport otoroshi.script._\nimport play.api.Logger\nimport play.api.mvc.{Result, Results}\nimport scala.util._\nimport scala.concurrent.{ExecutionContext, Future}\n\nclass MyTransformer extends RequestTransformer {\n\n val logger = Logger(\"my-transformer\")\n\n // implements the methods you want\n}\n\n// WARN: do not forget this line to provide a working instance of your transformer to Otoroshi\nnew MyTransformer()\n```\n\nYou can use the compile button to check if the script compiles, or code the transformer in your IDE (see next point).\n\nThen go to a service descriptor, scroll to the bottom of the page, and select your transformer in the list\n\n@@@ div { .centered-img }\n\n@@@\n\n## Providing a transformer from Java classpath\n\nYou can write your own transformer using your favorite IDE. Just create an SBT project with the following dependencies. It can be quite handy to manage the source code like any other piece of code, and it avoid the compilation time for the script at Otoroshi startup.\n\n```scala\nlazy val root = (project in file(\".\")).\n settings(\n inThisBuild(List(\n organization := \"com.example\",\n scalaVersion := \"2.12.7\",\n version := \"0.1.0-SNAPSHOT\"\n )),\n name := \"request-transformer-example\",\n resolvers += Resolver.bintrayRepo(\"maif\", \"maven\"),\n libraryDependencies += \"fr.maif.otoroshi\" %% \"otoroshi\" % \"1.x.x\"\n )\n```\n\nWhen your code is ready, create a jar file \n\n```\nsbt package\n```\n\nand add the jar file to the Otoroshi classpath\n\n```sh\njava -Dotoroshi.scripts.enabled=true -cp \"/path/to/transformer.jar:$/path/to/otoroshi.jar\" play.core.server.ProdServerStart\n```\n\nthen, in your service descriptor, you can chose your transformer in the list. If you want to do it from the API, you have to defined the transformerRef using `cp:` prefix like \n\n```json\n{\n \"transformerRef\": \"cp:my.class.package.MyTransformer\"\n}\n```\n\n## Getting custom configuration from the Otoroshi config. file\n\nLet say you need to provide custom configuration values for a script, then you can customize a configuration file of Otoroshi\n\n```hocon\ninclude \"application.conf\"\n\notoroshi {\n scripts {\n enabled = true\n }\n}\n\nmy-transformer {\n env = \"prod\"\n maxRequestBodySize = 2048\n maxResponseBodySize = 2048\n}\n```\n\nthen start Otoroshi like\n\n```sh\njava -Dconfig.file=/path/to/custom.conf -jar otoroshi.jar\n```\n\nthen, in your transformer, you can write something like \n\n```scala\npackage com.example.otoroshi\n\nimport akka.stream.Materializer\nimport akka.stream.scaladsl._\nimport akka.util.ByteString\nimport env.Env\nimport models.{ApiKey, PrivateAppsUser, ServiceDescriptor}\nimport otoroshi.script._\nimport play.api.Logger\nimport play.api.mvc.{Result, Results}\nimport scala.util._\nimport scala.concurrent.{ExecutionContext, Future}\n\nclass BodyLengthLimiter extends RequestTransformer {\n\n override def transformResponseBody(\n snowflake: String,\n body: Source[ByteString, _],\n rawResponse: HttpResponse,\n otoroshiResponse: HttpResponse,\n desc: ServiceDescriptor,\n apiKey: Option[ApiKey],\n user: Option[PrivateAppsUser])(implicit env: Env, ec: ExecutionContext, mat: Materializer): Source[ByteString, _] = {\n val max = env.configuration.getOptional[Long](\"my-transformer.maxResponseBodySize\").getOrElse(Long.MaxValue)\n body.limitWeighted(max)(_.size)\n }\n\n override def transformRequestBody(\n snowflake: String,\n body: Source[ByteString, _],\n rawRequest: HttpRequest,\n otoroshiRequest: HttpRequest,\n desc: ServiceDescriptor,\n apiKey: Option[ApiKey],\n user: Option[PrivateAppsUser])(implicit env: Env, ec: ExecutionContext, mat: Materializer): Source[ByteString, _] = {\n val max = env.configuration.getOptional[Long](\"my-transformer.maxRequestBodySize\").getOrElse(Long.MaxValue)\n body.limitWeighted(max)(_.size)\n }\n}\n```\n\n## Using a library that is not embedded in Otoroshi\n\nJust use the `classpath` option when running Otoroshi\n\n```sh\njava -Dotoroshi.scripts.enabled=true -cp \"/path/to/library.jar:$/path/to/otoroshi.jar\" play.core.server.ProdServerStart\n```\n\nBe carefull as your library can conflict with other libraries used by Otoroshi and affect its stability\n" + }, + { + "name": "service-mesh.md", + "id": "/topics/service-mesh.md", + "url": "/topics/service-mesh.html", + "title": "Service mesh with Otoroshi", + "content": "# Service mesh with Otoroshi\n\n[Service mesh](http://philcalcado.com/2017/08/03/pattern_service_mesh.html) is a pattern that gained a lot of traction lately with tools like [Kubernetes](https://kubernetes.io/) and [Istio](https://istio.io/) using patterns like [sidecar container](https://kubernetes.io/blog/2015/06/the-distributed-system-toolkit-patterns/#example-1-sidecar-containers). It has the advantages to move the complexity of calling other services outside the application. For the application, it's just a local call and the proxy handles apikeys, throttling, quotas, resilience, tracing, etc ...\n\n## Architecture\n\nLet's say that we have 4 service that want to talk to each other. But we don't want to handle the complexity of calling those services with their own apikey, etc ...\n\n@@@ div { .centered-img }\n\n@@@\n\nSo we are going to build an infrastructure where each service has a sidecar Otoroshi that will handle the calls to other services\n\n@@@ div { .centered-img }\n\n@@@\n\n## Configuration\n\nIn this infrastructure, each service has its own service descriptor in Otoroshi that points to another Otoroshi instance. The challenge here is to dynamically change the target of any service when the current call is supposed to access th actual app.\n\nTo do that, each Otoroshi instance (that is part of the same cluster and use the same database) will have a configuration slightly different configuration than the other ones. \n\n```hocon\nsidecar {\n serviceId = \"my-local-service-id\" # the id of the local sidecar app\n target = \"http://127.0.0.1:56876\" # the local service target, it should be on 127.0.0.1 but can be on another ip address\n from = \"127.0.0.1\" # the ip address authorized to actualy access the local app, it should be on 127.0.0.1 but can be on another ip address\n strict = true # use actual remote address or remote address/proxied remote address\n apikey {\n clientId = \"my-apikey-id\" # the apikey client id to access other services\n }\n}\n```\n\n## Configuration using env. variables\n\nYou can also only env. variables to configure sidecar Otoroshi instances \n\n```sh\nSIDECAR_SERVICE_ID=my-local-service-id\nSIDECAR_TARGET=http://127.0.0.1:56876\nSIDECAR_FROM=127.0.0.1\nSIDECAR_STRICT=true\nSIDECAR_APIKEY_CLIENT_ID=my-apikey-id\n```\n\n## Example\n\nYou can find a full example of a simple service mesh using Otoroshi [here](https://github.com/MAIF/otoroshi/tree/master/demos/service-mesh)." + }, + { + "name": "snow-monkey.md", + "id": "/topics/snow-monkey.md", + "url": "/topics/snow-monkey.html", + "title": "Chaos engineering with the Snow Monkey", + "content": "# Chaos engineering with the Snow Monkey\n\nNihonzaru (the Snow Monkey) is the chaos engineering tool provided by Otoroshi. You can access it at `Settings (cog icon) / Snow Monkey`.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Chaos engineering\n\nOtoroshi offers some tools to introduce [chaos engineering](https://principlesofchaos.org/) in your everyday life. With chaos engineering, you will improve the resilience of your architecture by creating faults in production on running systems. With [Nihonzaru (the snow monkey)](https://en.wikipedia.org/wiki/Japanese_macaque) Otoroshi helps you to create faults on http request/response handled by Otoroshi. \n\n@@@ div { .centered-img }\n\n@@@\n\n## Settings\n\n@@@ div { .centered-img }\n\n@@@\n\nThe snow monkey let you define a few settings to work properly :\n\n* **Include user facing apps.**: you want to create fault in production, but maybe you don't want your users to enjoy some nice snow monkey generated error pages. Using this switch let you include of not user facing apps (ui apps). Each service descriptor has a `User facing app switch` that will be used by the snow monkey.\n* **Dry run**: when dry run is enabled, outages will be registered and will generate events and alerts (in the otoroshi eventing system) but requests won't be actualy impacted. It's a good way to prepare applications to the snow monkey habits\n* **Outage strategy**: Either `AllServicesPerGroup` or `OneServicePerGroup`. It means that only one service per group or all services per groups will have `n` outages (see next bullet point) during the snow monkey working period\n* **Outages per day**: during snow monkey working period, each service per group or one service per group will have only `n` outages registered \n* **Working period**: the snow monkey only works during a working period. Here you can defined when it starts and when it stops\n* **Outage duration**: here you can defined the bounds for the random outage duration when an outage is created on a service\n* **Impacted groups**: here you can define a list of service groups impacted by the snow monkey. If none is specified, then all service groups will be impacted\n\n## Faults\n\nWith the snow monkey, you can generate four types of faults\n\n* **Large request fault**: Add trailing bytes at the end of the request body (if one)\n* **Large response fault**: Add trailing bytes at the end of the response body\n* **Latency injection fault**: Add random response latency between two bounds\n* **Bad response injection fault**: Create predefined responses with custom headers, body and status code\n\nEach fault let you define a ratio for impacted requests. If you specify a ratio of `0.2`, then 20% of the requests for the impacte service will be impacted by this fault\n\n@@@ div { .centered-img }\n\n@@@\n\nThen you juste have to start the snow monkey and enjoy the show ;)\n\n@@@ div { .centered-img }\n\n@@@\n\n## Current outages\n\nIn the last section of the snow monkey page, you can see current outages (per service), when they started, their duration, etc ...\n\n@@@ div { .centered-img }\n\n@@@" + }, + { + "name": "ssl.md", + "id": "/topics/ssl.md", + "url": "/topics/ssl.html", + "title": "SSL/TLS termination with Otoroshi", + "content": "# SSL/TLS termination with Otoroshi\n\nOtoroshi can be used as an SSL/TLS termination. It is enabled by default but you can customise HTTPS port with `https.port` config. and env. var `HTTPS_PORT`. You can create upload any certificate you want in the Otoroshi UI or using the API. Just go to `settings (cog icon) / SSL certificates`.\n\n@@@ note { title=\"Experimental Feature\" }\nDynamic SSL/TLS termination is an experimental feature. It can change until it becomess an official feature\n@@@\n\n@@@ note { title=\"TLS 1.3 support\" }\nOtoroshi does support TLS 1.3 when used in combination with JDK 11\n\n\n@@@\n\n@@@ div { .centered-img }\n\n@@@\n\nHere you can add your own certificates, your own CA and even create self signed certificates or certificates from CAs. You can enable auto renewal of thoses self signed certificates or certificates generated. Certificates have to be created with the certificate chain and the private key in PEM format with no password on the private key.\n\nYou can remove the password of a key with the following command\n\n```sh\nopenssl rsa -in keywithpassword.key -out keywithoutpassword.key\n```\n\n@@@ div { .centered-img }\n\n@@@\n\n" + }, + { + "name": "1-groups.md", + "id": "/usage/1-groups.md", + "url": "/usage/1-groups.html", + "title": "Managing service groups", + "content": "# Managing service groups\n\nGo to `settings (cog icon) / All service groups` to access the list of service groups.\n\n@@@ div { .centered-img }\n\n@@@\n\nAnd you should see the list of existing `Service groups`.\n\n@@@ div { .centered-img }\n\n@@@\n\nBut what is a `Service group` anyway ?\n\n## Otoroshi entities\n\nThere are 3 major entities at the core of Otoroshi :\n\n* **service groups**\n* service descriptors\n* api keys\n\n@@@ div { .centered-img }\n\n@@@\n\nA `service group` is just some kind of logical container for `service descriptors`. A `service group` also has some `api keys` assigned that will be used to access all the `service descriptors` contained in the `service group`.\n\n## Create a service group\n\nA `service group` is a really simple structure with an `id`, a name and a description. To create a new one, just click on the `Add item` button.\n\n@@@ div { .centered-img }\n\n@@@\n\nmodify the name and the description of the group\n\n@@@ div { .centered-img }\n\n@@@\n\nand click on `Create group`\n\n@@@ div { .centered-img }\n\n@@@\n\nThen, you should find your brand new `Service group` in the list of `Service groups`\n\n@@@ div { .centered-img }\n\n@@@\n\n## Update a service\n\nTo update a `Service group`, just click on the edit button of your `Service group`\n\n@@@ div { .centered-img }\n\n@@@\n\nUpdate the name and description of the `Service group` and click on the `Update group` button to validate name update.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Delete a service group\n\nTo delete a `Service group`, just click on the delete button of your `Service group`\n\n@@@ div { .centered-img }\n\n@@@\n\nFinally confirm the command\n\n@@@ div { .centered-img }\n\n@@@\n" + }, + { + "name": "2-services.md", + "id": "/usage/2-services.md", + "url": "/usage/2-services.html", + "title": "Managing services", + "content": "# Managing services\n\nNow let's create services. Services or `service descriptor` let you declare how to proxy a call from a domain name to another domain name (or multiple domain names). Let's say you have an API exposed on `http://192.168.0.42` and I want to expose it on `https://my.api.foo`. Otoroshi will proxy all calls to `https://my.api.foo` and forward them to `http://192.168.0.42`. While doing that, it will also log everyhting, control accesses, etc.\n\n## Otoroshi entities\n\nThere are 3 major entities at the core of Otoroshi\n\n* service groups\n* **service descriptors**\n* api keys\n\n@@@ div { .centered-img }\n\n@@@\n\nA `service descriptor` is contained in a `service group` and is allowed to be accessed by all the `api key`s authorized on the `service group`.\n\n## Create a service descriptor\n\nTo create a `service descriptor`, click on `Add service` on the Otoroshi sidebar. Then you will be asked to choose a name for the service what is the group of the service. You also have two buttons to create a new group and assign it to the service and create a new group with a name based on the service name.\n\nYou will have a serie of toggle buttons to\n\n* activate / deactivate a service\n* display maintenance page for a service\n* display contruction page for a service\n* enable otoroshi custom response headers containing request id, latency, etc \n* force https usage on the exposed service\n\nThen, you will be able to choose the URL that will be used to reach your new service on Otoroshi.\n\n@@@ div { .centered-img #service-flags }\n\n@@@\n\nIn the `service targets` section, you will be able to choose where the call will be forwarded. You can use multiple targets, in that case, Otoroshi will perform a round robin load balancing between the targets. If the `override Host header` toggle is one, the host header will be changed for the host of the target. For example, if you request `http://www.oto.tools/api` with a target to `http://www-internal.service.local/api`, the target will receive a `Host: www-internal.service.local` instead of `Host: www.oto.tools`.\n\nYou can also specify a target root, if you say that the target root is `/foo/`, then any call to `https://my.api.foo` will call `http://192.168.0.42/foo/` and nay call to `https://my.api.foo/bar` will call `http://192.168.0.42/foo/bar`.\n\nIn the URL patterns section, you will be able to choose, URL by URL which is private and which is public. By default, all services are private and each call must provide an `api key`. But sometimes, you need to access a service publicly. In that case, you can provide patterns (regex) to make some or all URL public (for example with the pattern `/.*`). You also have a `private pattern` field to restrict public patterns.\n\n@@@ div { .centered-img #targets }\n\n@@@\n\n### Otoroshi exchange protocol\n\nIf you enable secure communication for a given service, you will have to add a filter on the target application that will take the `Otoroshi-State` header and return it in a header named `Otoroshi-State-Resp`. Otoroshi is also sending a `JWT token`in a header named `Otoroshi-Claim` that the target app can validate.\n\n@@@ div { .centered-img }\n\n@@@\n\nThe `Otoroshi-Claim` is a JWT token containing some informations about the service that is called and the client if available. \n\nBy default, the otoroshi jwt token is signed with the `app.claim.sharedKey` config property (or using the `$CLAIM_SHAREDKEY` env. variable) and uses the `HMAC512` signing algorythm. But it is possible to customize how the token is signed from the service descriptor page in the `Otoroshi exchange protocol` section. \n\n@@@ div { .centered-img }\n\n@@@\n\nusing another signing algo.\n\n@@@ div { .centered-img }\n\n@@@\n\nhere you can choose the signing algorithm and the secret/keys used. You can use syntax like `${env.MY_ENV_VAR}` or `${config.my.config.path}` to provide secret/keys values. \n\nFor example, for a service named `my-service` with a signing key `secret` with `HMAC512` signing algorythm, the basic JWT token that will be sent should look like the following\n\n```\neyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiItLSIsImF1ZCI6Im15LXNlcnZpY2UiLCJpc3MiOiJPdG9yb3NoaSIsImV4cCI6MTUyMTQ0OTkwNiwiaWF0IjoxNTIxNDQ5ODc2LCJqdGkiOiI3MTAyNWNjMTktMmFjNy00Yjk3LTljYzctMWM0ODEzYmM1OTI0In0.mRcfuFVFPLUV1FWHyL6rLHIJIu0KEpBkKQCk5xh-_cBt9cb6uD6enynDU0H1X2VpW5-bFxWCy4U4V78CbAQv4g\n```\n\nif you decode it, the payload will look something like\n\n```json\n{\n \"sub\": \"apikey_client_id\",\n \"aud\": \"my-service\",\n \"iss\": \"Otoroshi\",\n \"exp\": 1521449906,\n \"iat\": 1521449876,\n \"jti\": \"71025cc19-2ac7-4b97-9cc7-1c4813bc5924\"\n}\n```\n\nIf you want to validate the `Otoroshi-Claim` on the target app side to ensure that the input requests only comes from `Otoroshi`, you will have to write an HTTP filter to do the job. For instance, if you want to write a filter to make sure that requests only comes from Otoroshi, you can write something like the following (using playframework 2.6).\n\nScala\n: @@snip [filter.scala](../snippets/filter.scala)\n\nJava\n: @@snip [filter.java](../snippets/filter.java)\n\n\n### Canary mode\n\nOtoroshi provides a feature called `Canary mode`. It lets you define new targets for a service, and route a percentage of the traffic on those targets. It's a good way to test a new version of a service before public release. As any client need to be routed to the same version of targets any time, Otoroshi will issue a special header and a cookie containing a `session id`. The header is named `Otoroshi-Canary-Id`.\n\n@@@ div { .centered-img }\n\n@@@\n\n### Service health check\n\nOtoroshi is also capable of checking the health of a service. You can define a URL that will be tested, and Otoroshi will ping that URL regularly. Will doing so, Otoroshi will pass a numeric value in a header named `Otoroshi-Health-Check-Logic-Test`. You can respond with a header named `Otoroshi-Health-Check-Logic-Test-Result` that contains the value of `Otoroshi-Health-Check-Logic-Test` + 42 to indicate that the service is working properly.\n\n@@@ div { .centered-img }\n\n@@@\n\n### Service circuit breaker\n\nIn Otoroshi, each service has its own client settings with a circuit breaker and some retry capabilities. In the `Client settings` section, you will be able to customize the client's behavior.\n\n@@@ div { .centered-img }\n\n@@@\n\n### Service settings\n\nYou can also provide some additionnal information about a given service, like an `Open API` descriptor, some metadata, a list of whitelisted/blacklisted ip addresses, etc.\n\nHere you can also define some headers that will be added to each request to the targets. And you will be able to define headers to route the call only if the defined header is present on the request.\n\n@@@ div { .centered-img #service-meta }\n\n@@@\n\n### Read only\n\nThe read only flag on a service descriptor means that this service can only be used with `HEAD`, `OPTIONS` and `GET` http verbs. You can also active the same flag on `ApiKey`s to be more specific on who cannot use write http verbs.\n\n### CORS \n\nIf you enabled this section, CORS will be automatically supported on the current service provider. The pre-flight request will be handled by Otoroshi. You can customize every CORS headers :\n\n@@@ div { .centered-img }\n\n@@@\n\n### Service authentication\n\nSee @ref:[Aauthentication](./9-auth.md)\n\n### Custom error templates\n\nFinally, you can define custom error templates that will be displayed when an error occurs when Otoroshi try to reach the target or when Otoroshi itself has an error. You can also define custom templates for maintenance and service pages.\n" + }, + { + "name": "3-apikeys.md", + "id": "/usage/3-apikeys.md", + "url": "/usage/3-apikeys.html", + "title": "Managing API keys", + "content": "# Managing API keys\n\nNow that you know how to create service groups and service descriptors, we will see how to create API keys.\n\n## Otoroshi entities\n\nThere are 3 major entities at the core of Otoroshi.\n\n* service groups\n* service descriptors\n* **api keys**\n\n@@@ div { .centered-img }\n\n@@@\n\nAn `API key` related to a `service group` to allow you to access any `service descriptor` contained in a `service group`. You can, of course, create multiple `API key` for a given `service group`.\n\nIn the Otoroshi admin dashboard, we chose to access `API keys` from `service descriptors` only, but when you access `API keys` for a `service descriptor`, you actually access `API keys` for the `service group` containing the `service descriptor`.\n\n`API keys` can be provided to Otoroshi through :\n\n* `Otoroshi-Authorization: Basic $base64(client_id:client_secret)` header, in that case, the `Otoroshi-Authorization` header will **not** be sent to the target. `Basic ` is optional.\n* `Authorization: Basic $base64(client_id:client_secret)` header, in that case, the `Authorization` header **will** be sent to the target\n* `Otoroshi-Token: Bearer $jwt_token` where the JWT token has been signed with the `API key` client secret, in that case, the `Otoroshi-Token` header will **not** be sent to the target. `Bearer ` is optional.\n* `Authorization: Bearer $jwt_token` where the JWT token has been signed with the `API key` client secret, in that case, the `Authorization` header **will** be sent to the target\n* `Cookie: access_token=$jwt_token;` where the JWT token has been signed with the `API key` client secret, in that case, the cookie named `access_token` **will** be sent to the target\n* `Otoroshi-Client-Id` and `Otoroshi-Client-Secret` headers, in that case the `Otoroshi-Client-Id` and `Otoroshi-Client-Secret` headers will not be sent to the target.\n\n## List API keys for a service descriptor\n\nGo to a service descriptor using `All services` quick link in the sidebar or the search box.\n\n@@@ div { .centered-img }\n\n@@@\n\nSelect a `service descriptor`.\n\n@@@ div { .centered-img }\n\n@@@\n\nClick on `API keys` in the sidebar\n\n@@@ div { .centered-img }\n\n@@@\n\nYou should see the list of API keys for that `service descriptor`\n\n@@@ div { .centered-img }\n\n@@@\n\n## Create an API key for a service descriptor\n\n@@@ div { .centered-img }\n\n@@@\n\nYou can add a name for your new API key, you can also change client's id and client's secret. You can also configure the throttling rate of the API key (calls per second), and the authorized number of call per day and per month. You may also activate or de-activate the api key from that screen.\n\nInformations about current quotas usage will be returned in response headers.\n\n* `Otoroshi-Daily-Calls-Remaining` : authorized calls remaining for this day\n* `Otoroshi-Monthly-Calls-Remaining` : authorized calls remaining for this month\n* `Otoroshi-Proxy-Latency` : latency induced by Otoroshi\n* `Otoroshi-Upstream-Latency` : latency between Otoroshi and target\n\n@@@ div { .centered-img #quotas }\n\n@@@\n\n@@@ warning\nDaily and monthly quotas are based on the following rules :\n\n* daily quota is computed between 00h00:00.000 and 23h59:59.999\n* monthly qutoas is computed between the first day of the month at 00h00:00.000 and the last day of the month at 23h59:59.999\n@@@\n\n## Update an API key\n\nTo update an `API key`, just click on the edit button of your `API key`\n\n@@@ div { .centered-img }\n\n@@@\n\nUpdate the name, secret, state and quotas (if needed) of the `API key` and click on the `Update API key` button\n\n@@@ div { .centered-img }\n\n@@@\n\n## Delete an API key\n\nTo delete an `API key`, just click on the delete button of your `API key`\n\n@@@ div { .centered-img }\n\n@@@\n\nand confirm the command\n\n@@@ div { .centered-img }\n\n@@@\n\n### Read only\n\nThe read only flag on an `ApiKey` this apikey can only use allowed services with `HEAD`, `OPTIONS` and `GET` http verbs.\n\n## Use a JWT token to pass an API key\n\nYou can use a JWT token to pass an API key to Otoroshi. \nYou can use `Otoroshi-Authorization: Bearer $jwt_token`, `Authorization: Bearer $jwt_token` header and `Cookie: access_token=$jwt_token;` to pass the JWT token.\nYou have to create a JWT token with a signing algorythm that can be `HS256` or `HS512`. Then you have to provide an `iss` claim with the value of your API key `clientId` and sign the JWT token with your API key `clientSecret`.\n\nFor example, with an API key like `clientId=abcdef` and `clientSecret=1234456789`, your JWT token should look like\n\n```json\n{\n \"alg\": \"HS256\",\n \"typ\": \"JWT\"\n}\n{\n \"iss\":\"abcdef\",\n \"name\": \"John Doe\",\n \"admin\": true\n}\n```\n\nin that case, when you sign the token with the secret of the API key `1234456789`, the signature will be `_eancnYCD3makSSox2v2xErjNYkRtcX558QiJGCbino`, resulting in a encoded JWT header like\n\n```\neyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.\neyJpc3MiOiJhYmNkZWYiLCJuYW1lIjoiSm9obiBEb2UiLCJhZG1pbiI6dHJ1ZX0.\n_eancnYCD3makSSox2v2xErjNYkRtcX558QiJGCbino\n```\n" + }, + { + "name": "4-monitor.md", + "id": "/usage/4-monitor.md", + "url": "/usage/4-monitor.html", + "title": "Monitoring services", + "content": "# Monitoring services\n\nOnce you have declared services, you can monitor them with Otoroshi.\n\n@@@ warning\nYou have to use [Elastic](https://www.elastic.co) to enable analytics features in Otoroshi\n@@@\n\nOnce you have setup @ref:[Otoroshi events push to an elastic cluster](../integrations/analytics.md) (through webhooks, kafka, or elastic integration) you can setup Otoroshi events read from an elastic cluster. Go to `settings (cog icon) / Danger Zone` and expand the `Analytics: Elastic dashboard datasource (read)` section.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Service healthcheck\n\nIf you have defined an health check URL in the service descriptor, you can access the health check page from the sidebar of the service page.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Service live stats\n\nYou can also monitor live stats like total of served request, average response time, average overhead, etc. The live stats page can be accessed from the sidebar of the service page.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Service analytics\n\nYou can also get some aggregated metrics. The analytics page can be accessed from the sidebar of the service page.\n\n@@@ div { .centered-img }\n\n@@@\n" + }, + { + "name": "5-sessions.md", + "id": "/usage/5-sessions.md", + "url": "/usage/5-sessions.html", + "title": "Managing sessions", + "content": "# Managing sessions\n\nWith Otoroshi you can manage sessions of connected users and you can discard sessions whenever you want. Session last 24h by default and you can customize them with `app.backoffice.session.exp` and `app.privateapps.session.exp` @ref:[config keys](../firstrun/configfile.md)\n\n## Admin. sessions\n\nTo see last current admin session on Otoroshi from the UI, go to `settings (cog icon) / Admins sessions`. Here you can discard individual sessions or all sessions at once using `Discard session` and `Discard all sessions` buttons.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Private apps. session\n\nTo see last current admin session on Otoroshi from the UI, go to `settings (cog icon) / Priv. apps sessions`. Here you can discard individual sessions or all sessions at once using `Discard session` and `Discard all sessions` buttons.\n\n@@@ div { .centered-img }\n\n@@@\n" + }, + { + "name": "6-audit.md", + "id": "/usage/6-audit.md", + "url": "/usage/6-audit.html", + "title": "Auditing Otoroshi", + "content": "# Auditing Otoroshi\n\nWith Otoroshi, any admin action and any sucpicious/alert action is recorded. These records are stored in Otoroshi's datastore (only the last n records, defined by the `app.events.maxSize` @ref:[config key](../firstrun/configfile.md)). All the records can be send through the analytics mechanism (WebHook, Kafka, Elastic) for external and/or further usage. We recommand sending away those records for security reasons.\n\n@@@ warning\nYou have to use [Elastic](https://www.elastic.co) to enable analytics features in Otoroshi. See @ref:[Elastic setup section](../integrations/analytics.md)\n@@@\n\n## Audit trail\n\nTo see last `app.events.maxSize` admin actions on Otoroshi from the UI, go to `settings (cog icon) / Audit log`.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Alerts\n\nTo see last `app.events.maxSize` alerts on Otoroshi from the UI, go to `settings (cog icon) / Alerts log`.\n\n@@@ div { .centered-img }\n\n@@@\n\n## List of possible alerts\n\n```\nMaxConcurrentRequestReachedAlert\nCircuitBreakerOpenedAlert\nCircuitBreakerClosedAlert\nSessionDiscardedAlert\nSessionsDiscardedAlert\nPanicModeAlert\nOtoroshiExportAlert\nU2FAdminDeletedAlert\nBlackListedBackOfficeUserAlert\nAdminLoggedInAlert\nAdminFirstLogin\nAdminLoggedOutAlert\nDbResetAlert\nDangerZoneAccessAlert\nGlobalConfigModification\nRevokedApiKeyUsageAlert\nServiceGroupCreatedAlert\nServiceGroupUpdatedAlert\nServiceGroupDeletedAlert\nServiceCreatedAlert\nServiceUpdatedAlert\nServiceDeletedAlert\nApiKeyCreatedAlert\nApiKeyUpdatedAlert\nApiKeyDeletedAlert\n```\n" + }, + { + "name": "7-metrics.md", + "id": "/usage/7-metrics.md", + "url": "/usage/7-metrics.html", + "title": "Otoroshi global metrics", + "content": "# Otoroshi global metrics\n\nOtoroshi provide some global metrics about services usage. Go to `settings (cog icon) / Global Ananlytics`\n\n@@@ warning\nYou have to use [Elastic](https://www.elastic.co) to enable analytics features in Otoroshi. See @ref:[Elastic setup section](../integrations/analytics.md)\n@@@\n\n@@@ div { .centered-img }\n\n@@@\n" + }, + { + "name": "8-importsexports.md", + "id": "/usage/8-importsexports.md", + "url": "/usage/8-importsexports.html", + "title": "Import and export", + "content": "# Import and export\n\nWith Otoroshi you can easily save the current state of the proxy and restore it later. Go to `settings (cog icon) / Danger Zone` and scroll to the bottom of the page\n\n## Full export\n\nClick on the `Full export` button.\n\n@@@ div { .centered-img }\n\n@@@\n\nYour browser will start to download a JSON file containing the internal state of your Otoroshi cluster.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Full import\n\nIf you want to restore an export, go to `settings (cog icon) / Danger Zone` and scroll to the bottom of the page. Click on the `Recover from full export file` button\n\n@@@ div { .centered-img }\n\n@@@\n\nChoose export file on your system.\n\n@@@ div { .centered-img }\n\n@@@\n\nClick on the `Flush datastore and import ...` button, confirm and you will be logged out.\n\n@@@ div { .centered-img }\n\n@@@\n" + }, + { + "name": "9-auth.md", + "id": "/usage/9-auth.md", + "url": "/usage/9-auth.html", + "title": "Authentication", + "content": "# Authentication\n\nYou can create auth. configuration in Otoroshi. Just go to `settings (cog icon) / Global auth. configs`.\n\n## OAuth 2\n\nCreate a new `Generic oauth2 provider` config and customize the following informations:\n\n```json\n{\n \"clientId\": \"xxxx\",\n \"clientSecret\": \"xxxx\",\n \"authorizeUrl\": \"http://yourOAuthServer/oauth/authorize\",\n \"tokenUrl\": \"http://yourOAuthServer/oauth/token\",\n \"userInfoUrl\": \"http://yourOAuthServer/userinfo\",\n \"loginUrl\": \"http://yourOAuthServer/login\",\n \"logoutUrl\": \"http://yourOAuthServer/logout?redirectQueryParamName=${redirect}\",\n \"accessTokenField\": \"access_token\",\n \"nameField\": \"name\",\n \"emailField\": \"email\",\n \"callbackUrl\": \"http://privateapps.oto.tools/privateapps/generic/callback\"\n}\n```\n\nIf used for BackOffice authentication, the callback url should be `http://otoroshi.oto.tools/backoffice/auth0/callback`.\n\nFor `logoutUrl`, `redirectQueryParamName` is a parameter with a name specific to your OAuth2 provider (for example, in Auth0, this parameter is called `returnTo`, in Kecloak it is called `redirect_uri`).\n\nif you are using a [KeyCloak](https://www.keycloak.org/) server, you can configure it this way, assuming you are using the master realm and you created a new client with a client secret, callback urls set to `http://privateapps.oto.tools/*`.\n\n```json\n{\n \"clientId\": \"clientId\",\n \"clientSecret\": \"clientSecret\",\n \"authorizeUrl\": \"http://keycloakHost/auth/realms/master/protocol/openid-connect/auth\",\n \"tokenUrl\": \"http://keycloakHost/auth/realms/master/protocol/openid-connect/token\",\n \"userInfoUrl\": \"http://keycloakHost/auth/realms/master/protocol/openid-connect/userinfo\",\n \"loginUrl\": \"http://keycloakHost/auth/realms/master/protocol/openid-connect/auth\",\n \"logoutUrl\": \"http://keycloakHost/auth/realms/master/protocol/openid-connect/logout?redirect_uri=${redirect}\",\n \"accessTokenField\": \"access_token\",\n \"nameField\": \"name\",\n \"emailField\": \"email\",\n \"callbackUrl\": \"http://privateapps.oto.tools/privateapps/generic/callback\"\n}\n```\n\n## Ldap\n\nCreate a new `Ldap auth. provider` config and customize the following informations:\n\n```json\n{\n \"serverUrl\": \"ldap://ldap.forumsys.com:389\",\n \"searchBase\": \"dc=example,dc=com\",\n \"groupFilter\": \"ou=chemists\",\n \"searchFilter\": \"(mail=${username})\",\n \"adminUsername\": \"cn=read-only-admin,dc=example,dc=com\",\n \"adminPassword\": \"password\",\n \"nameField\": \"cn\",\n \"emailField\": \"mail\"\n}\n```\n\n## In Memory\n\nCreate a new `In memory auth. provider` config and then you will be able to create new users. To set the password, just click on the `Set password` button. It will generate a BCrypt hash of the password you typed.\n\n## Auth0\n\nCreate a new OAuth 2 config and add the following informations:\n\n```json\n{\n \"clientId\": \"yourAuth0ClientId\",\n \"clientSecret\": \"yourAuth0ClientSecret\",\n \"authorizeUrl\": \"https://yourAuth0Domain/authorize\",\n \"tokenUrl\": \"https://yourAuth0Domain/oauth/token\",\n \"userInfoUrl\": \"https://yourAuth0Domain/userinfo\",\n \"loginUrl\": \"https://yourAuth0Domain/authorize\",\n \"logoutUrl\": \"https://yourAuth0Domain/v2/logout?returnTo=${redirect}\",\n \"accessTokenField\": \"access_token\",\n \"nameField\": \"name\",\n \"emailField\": \"email\",\n \"otoroshiDataField\": \"app_metadata | otoroshi_data\",\n \"callbackUrl\": \"http://privateapps.oto.tools/privateapps/generic/callback\"\n}\n```\n\nIf you enable Otoroshi exchange protocol, the JWT xill have the following fields (all optional)\n\n* `email`\n* `name`\n* `picture`\n* `user_id`\n* `given_name`\n* `family_name`\n* `gender`\n* `locale`\n* `nickname`\n\nIn Auth0, the metadata is a flat object placed in the `profile / http://yourdomain/app_metadata / otoroshi_data`. You might need to write an Auth0 rule to copy app metadata under `http://yourdomain/app_metadata`, the `http://yourdomain/app_metadata` value is a config property `app.appMeta`. The rule could be something like the following\n\n```js\nfunction (user, context, callback) {\n var namespace = 'http://yourdomain/';\n context.idToken[namespace + 'user_id'] = user.user_id;\n context.idToken[namespace + 'user_metadata'] = user.user_metadata;\n context.idToken[namespace + 'app_metadata'] = user.app_metadata;\n callback(null, user, context);\n}\n```" + }, + { + "name": "index.md", + "id": "/usage/index.md", + "url": "/usage/index.html", + "title": "Using Otoroshi", + "content": "# Using Otoroshi\n\nNow we will see how to use Otoroshi for basic tasks that will be useful for your day to day work with Otoroshi.\n\n@@@ index\n\n* [create group](./1-groups.md)\n* [create service](./2-services.md)\n* [create API Keys](./3-apikeys.md)\n* [monitor service](./4-monitor.md)\n* [sessions management](./5-sessions.md)\n* [Audit trail and alerts](./6-audit.md)\n* [Global metrics](./7-metrics.md)\n* [Exports and imports](./8-importsexports.md)\n* [Authentication](./9-auth.md)\n\n@@@\n" + }, + { + "name": "videos.md", + "id": "/videos.md", + "url": "/videos.html", + "title": "Video tutorials", + "content": "# Video tutorials\n\n## Create a service restricted by an api key\n\nhere you will learn how to create a simple service that will proxy an existing api and how to secure it using an api key.\n\n\n\n## Use CLI to create load balanced services with retry\n\nhere you will learn how to create a service using the Otoroshi CLI and how to use load balancing with retry features of Otoroshi.\n\n" + } +] \ No newline at end of file diff --git a/docs/manual/content.json b/docs/manual/content.json new file mode 100644 index 0000000000..58f26faab4 --- /dev/null +++ b/docs/manual/content.json @@ -0,0 +1 @@ +[{"name":"about.md","id":"/about.md","url":"/about.html","title":"About Otoroshi","content":"# About Otoroshi\n\nAt the beginning of 2017, we had the need to create a new environment to be able to create new \"digital\" products very quickly in an agile fashion at MAIF. Naturally we turned to PaaS solutions and chose the excellent Clever-Cloud product to run our apps. \n\nWe also chose that every feature team will have the freedom to choose its own technological stack to build its product. It was a nice move but it has also introduced some challenges in terms of homogeneity for traceability, security, logging, ... because we did not want to force library usage in the products. We could have used something like Service Mesh Pattern but the deployement model of Clever-Cloud prevented us to do it.\n\nThe right solution was to use a reverse proxy or some kind of API Gateway able to provide tracability, logging, security with apikeys, quotas, DNS as a service locator, etc. We needed something easy to use, with a human friendly UI, a nice API to extends its features, true hot reconfiguration, able to generate internal events for third party usage. A couple of solutions were available at that time, but not one seems to fit our needs, there was always something missing, too complicated for our needs or not playing well with Clever-Cloud deployment model.\n\nAt some point, we tried to write a small prototype to explore what could be our dream reverse proxy. The design was very simple, there were some rough edges but every major feature needed was there waiting to be enhanced.\n\n**Otoroshi** was born and we decided to move ahead with our hairy monster :)\n\n## Philosophy \n\nEvery OSS product build at MAIF like Izanami follow a common philosophy. \n\n* the services or API provided should be technology agnostic.\n* http first: http is the right answer to the previous quote \n* api First: The UI is just another client of the api. \n* secured: The services exposed need authentication for both humans or machines \n* event based: The services should expose a way to get notified of what happened inside. \n"},{"name":"api.md","id":"/api.md","url":"/api.html","title":"Admin REST API","content":"# Admin REST API\n\nOtoroshi provides a fully featured REST admin API to perform almost every operation possible in the Otoroshi dashboard. The Otoroshi dashbaord is just a regular consumer of the admin API.\n\nUsing the admin API, you can do whatever you want and enhance your Otoroshi instances with a lot of features that will feet your needs.\n\nOtoroshi also provides some connectors that uses the Otoroshi admin API to automate Otorshi's instances when used with stuff like containers orchestrators. For more informations about that, just go to the @ref:[third party integrations chapter](./integrations/index.md)\n\n## Swagger descriptor\n\nThe Otoroshi admin API is described using OpenAPI format and is available at :\n\nhttps://maif.github.io/otoroshi/manual/code/swagger.json\n\nEvery Otoroshi instance provides its own embedded OpenAPI descriptor at :\n\nhttp://otoroshi.oto.tools:8080/api/swagger.json\n\n## Swagger documentation\n\nYou can read the OpenAPI descriptor in a more human friendly fashion using `Swagger UI`. The swagger UI documentation of the Otoroshi admin API is available at :\n\nhttps://maif.github.io/otoroshi/swagger-ui/index.html\n\nEvery Otoroshi instance provides its own embedded OpenAPI descriptor at :\n\nhttp://otoroshi.oto.tools:8080/api/swagger/ui\n\nYou can also read the swagger UI documentation of the Otoroshi admin API below :\n\n@@@ div { .swagger-frame }\n\n\n@@@\n"},{"name":"archi.md","id":"/archi.md","url":"/archi.html","title":"Architecture","content":"# Architecture\n\nWhen we started the development of Otoroshi, we had several classical patterns in mind like `Service gateway`, `Service locator`, `Circuit breakers`, etc ...\n\nAt start we thought about providing a bunch of librairies that would be included in each microservice or app to perform these tasks. But the more we were thinking about it, the more it was feeling weird, unagile, etc, it also prevented us to use any technical stack we wanted to use. So we decided to change our approach to something more universal.\n\nWe chose to make Otoroshi the central part of our microservices system, something between a reverse-proxy, a service gateway and a service locator where each call to a microservice (even from another microservice) must pass through Otoroshi. There are multiple benefits to do that, each call can be logged, audited, monitored, integrated with a circuit breaker, etc without imposing libraries and technical stack. Any service is exposed through its own domain and we rely only on DNS to handle the service location part. Any access to a service is secured by default with an api key and is supervised by a circuit breaker to avoid cascading failures.\n\n@@@ div { .centered-img }\n\n@@@\n\nOtoroshi tries to embrace our @ref:[global philosophy](./about.md#philosophy) by providing a full featured REST admin api, a gorgeous admin dashboard written in [React](https://reactjs.org/) that uses the api, by generating traffic events, alerts events, audit events that can be consumed by several channels. Otoroshi also supports a bunch of datastores to better match with different use cases.\n\n@@@ div { .centered-img }\n\n@@@\n"},{"name":"cli.md","id":"/cli.md","url":"/cli.html","title":"Rust CLI","content":"# Rust CLI\n\n@@@ warning\nOtoroshi had a CLI written in Rust until v1.4.2 but it's deprecated now. If you are interested in maintaining this tool, just tell us ;)\n@@@\n"},{"name":"clevercloud.md","id":"/connectors/clevercloud.md","url":"/connectors/clevercloud.html","title":"Clever Cloud *","content":"# Clever Cloud *\n\n@@@ warning\nThis is an experimental connector\n@@@\n\nThe documentation is not ready yet as it's an experimental connector, but you can read more about it [here](https://github.com/MAIF/otoroshi/tree/master/connectors/clevercloud).\n"},{"name":"index.md","id":"/connectors/index.md","url":"/connectors/index.html","title":"Connectors","content":"# Connectors\n\nOtoroshi provides a set of connectors to be used in some use cases that could be useful :\n\n* [rancher connector](https://github.com/MAIF/otoroshi/tree/master/connectors/rancher) : a connector to synchronize a rancher cluster with Otoroshi on the fly\n* [kubernetes connector](https://github.com/MAIF/otoroshi/tree/master/connectors/kubernetes) : a connector to synchronize a kubernetes cluster with Otoroshi on the fly\n* [Clever-Cloud connector](https://github.com/MAIF/otoroshi/tree/master/connectors/clevercloud) : a connector to synchronize a Clever-Cloud organization with Otoroshi on the fly\n\nIf you want to build your own connector, Otoroshi provides a common library for synchronization. You can find the code here :\n\nhttps://github.com/MAIF/otoroshi/tree/master/connectors/common\n\n@@@ index\n\n* [clevercloud](./clevercloud.md)\n* [kubernetes](./kubernetes.md)\n* [rancher](./rancher.md)\n\n@@@\n"},{"name":"kubernetes.md","id":"/connectors/kubernetes.md","url":"/connectors/kubernetes.html","title":"Kubernetes *","content":"# Kubernetes *\n\n@@@ warning\nThis is an experimental connector\n@@@\n\nThe documentation is not ready yet as it's an experimental connector, but you can read more about it [here](https://github.com/MAIF/otoroshi/tree/master/connectors/kubernetes).\n"},{"name":"rancher.md","id":"/connectors/rancher.md","url":"/connectors/rancher.html","title":"Rancher *","content":"# Rancher *\n\n@@@ warning\nThis is an experimental connector\n@@@\n\nThe documentation is not ready yet as it's an experimental connector, but you can read more about it [here](https://github.com/MAIF/otoroshi/tree/master/connectors/rancher).\n"},{"name":"aws-beanstalk.md","id":"/deploy/aws-beanstalk.md","url":"/deploy/aws-beanstalk.html","title":"AWS - Elastic Beanstalk","content":"# AWS - Elastic Beanstalk\n\nNow you want to use Otoroshi on AWS. There are multiple options to deploy Otoroshi on AWS, \nfor instance :\n\n* You can deploy the [Docker image](../getotoroshi/fromdocker.md) on [Amazon ECS](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html)\n* You can create a basic [Amazon EC2](https://docs.aws.amazon.com/fr_fr/AWSEC2/latest/UserGuide/concepts.html), access it via SSH, then \ndeploy the @ref:[otoroshi.jar](../firstrun/run.md#from-jar-file) \n* Or you can use [AWS Elastic Beanstalk](https://aws.amazon.com/fr/elasticbeanstalk)\n\nIn this section we are going to cover how to deploy Otoroshi on [AWS Elastic Beanstalk](https://aws.amazon.com/fr/elasticbeanstalk). \n\n## AWS Elastic Beanstalk Overview\nUnlike Clever Cloud, to deploy an application on AWS Elastic Beanstalk, you don't link your app to your VCS repository, push your code and expect it to be built and run.\n\nAWS Elastic Beanstalk does only the run part. So you have to handle your own build pipeline, upload a Zip file containing your runnable, then AWS Elastic Beanstalk will take it from there. \n \nEg: for apps running on the JVM (Scala/Java/Kotlin) a Zip with the jar inside would suffice, for apps running in a Docker container, a Zip with the DockerFile would be enough. \n\n\n## Prepare your deployment target\nActually, there are 2 options to build your target. \n\nEither you create a DockerFile from this [Docker image](../getotoroshi/fromdocker.md), build a zip, and do all the Otoroshi custom configuration using ENVs.\n\nOr you download the @ref:[otoroshi.jar](../getotoroshi/frombinaries.md), do all the Otoroshi custom configuration using your own otoroshi.conf, and create a DockerFile that runs the jar using your otoroshi.conf. \n\nFor the second option your DockerFile would look like this :\n\n```dockerfile\nFROM openjdk:8\nVOLUME /tmp\nEXPOSE 8080\nADD otoroshi.jar otoroshi.jar\nADD otoroshi.conf otoroshi.conf\nRUN sh -c 'touch /otoroshi.jar'\nENV JAVA_OPTS=\"\"\nENTRYPOINT [ \"sh\", \"-c\", \"java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -Dconfig.file=/otoroshi.conf -jar /otoroshi.jar\" ]\n``` \n \nI'd recommend the second option.\n \nNow Zip your target (Jar + Conf + DockerFile) and get ready for deployment. \n\n## Create an Otoroshi instance on AWS Elastic Beanstalk\nFirst, go to [AWS Elastic Beanstalk Console](https://eu-west-3.console.aws.amazon.com/elasticbeanstalk/home?region=eu-west-3#/welcome), don't forget to sign in and make sure that you are in the good region (eg : eu-west-3 for Paris).\n\nHit **Get started** \n\n@@@ div { .centered-img }\n\n@@@\n\nSpecify the **Application name** of your application, Otoroshi for example.\n\n@@@ div { .centered-img }\n\n@@@\n \nChoose the **Platform** of the application you want to create, in your case use Docker.\n\nFor **Application code** choose **Upload your code** then hit **Upload**.\n\n@@@ div { .centered-img }\n\n@@@\n\nBrowse the zip created in the [previous section](#prepare-your-deployment-target) from your machine. \n\nAs you can see in the image above, you can also choose an S3 location, you can imagine that at the end of your build pipeline you upload your Zip to S3, and then get it from there (I wouldn't recommend that though).\n \nWhen the upload is done, hit **Configure more options**.\n \n@@@ div { .centered-img }\n\n@@@ \n \nRight now an AWS Elastic Beanstalk application has been created, and by default an environment named Otoroshi-env is being created as well.\n\nAWS Elastic Beanstalk can manage multiple environments of the same application, for instance environments can be (prod, preprod, expriments...). \n\nOtoroshi is a bit particular, it doesn't make much sense to have multiple environments, since Otoroshi will handle all the requests from/to downstream services regardless of the environment. \n \nAs you see in the image above, we are now configuring the Otoroshi-env, the one and only environment of Otoroshi.\n \nFor **Configuration presets**, choose custom configuration, now you have a load balancer for your environment with the capacity of at least one instance and at most four.\nI'd recommend at least 2 instances, to change that, on the **Capacity** card hit **Modify**. \n\n@@@ div { .centered-img }\n\n@@@\n\nChange the **Instances** to min 2, max 4 then hit **Save**. For the **Scaling triggers**, I'd keep the default values, but know that you can edit the capacity config any time you want, it only costs a redeploy, which will be done automatically by the way.\n \nInstances size is by default t2.micro, which is a bit small for running Otoroshi, I'd recommend a t2.medium. \nOn the **Instances** card hit **Modify**.\n\n@@@ div { .centered-img }\n\n@@@\n\nFor **Instance type** choose t2.medium, then hit **Save**, no need to change the volume size, unless you have a lot of http call faults, which means a lot more logs, in that case the default volume size may not be enough.\n\nThe default environment created for Otoroshi, for instance Otoroshi-env, is a web server environment which fits in your case, but the thing is that on AWS Elastic Beanstalk by default a web server environment for a docker-based application, runs behind an Nginx proxy.\nWe have to remove that proxy. So on the **Software** card hit **Modify**.\n \n@@@ div { .centered-img }\n\n@@@ \n \nFor **Proxy server** choose None then hit **Save**.\n\nAlso note that you can set Envs for Otoroshi in same page (see image below). \n\n@@@ div { .centered-img }\n\n@@@ \n\nTo finalise the creation process, hit **Create app** on the bottom right.\n\nThe Otoroshi app is now created, and it's running which is cool, but we still don't have neither a **datastore** nor **https**.\n \n## Create an Otoroshi datastore on AWS ElastiCache\n\nBy default Otoroshi uses non persistent memory to store it's data, Otoroshi supports many kinds of datastores. In this section we will be covering Redis datastore. \n\nBefore starting, using a datastore hosted by AWS is not at all mandatory, feel free to use your own if you like, but if you want to learn more about ElastiCache, this section may interest you, otherwise you can skip it.\n\nGo to [AWS ElastiCache](https://eu-west-3.console.aws.amazon.com/elasticache/home?region=eu-west-3#) and hit **Get Started Now**.\n\n@@@ div { .centered-img }\n\n@@@ \n\nFor **Cluster engine** keep Redis.\n\nChoose a **Name** for your datastore, for instance otoroshi-datastore.\n\nYou can keep all the other default values and hit **Create** on the bottom right of the page.\n\nOnce your Redis Cluster is created, it would look like the image below.\n\n@@@ div { .centered-img }\n\n@@@ \n\n\nFor applications in the same security group as your cluster, redis cluster is accessible via the **Primary Endpoint**. Don't worry the default security group is fine, you don't need any configuration to access the cluster from Otoroshi.\n\nTo make Otoroshi use the created cluster, you can either use Envs `APP_STORAGE=redis`, `REDIS_HOST` and `REDIS_PORT`, or set `app.storage=redis`, `app.redis.host` and `app.redis.port` in your otoroshi.conf.\n\n## Create SSL certificate and configure your domain\n\nOtoroshi has now a datastore, but not yet ready for use. \n\nIn order to get it ready you need to :\n\n* Configure Otoroshi with your domain \n* Create a wildcard SSL certificate for your domain\n* Configure Otoroshi AWS Elastic Beanstalk instance with the SSL certificate \n* Configure your DNS to redirect all traffic on your domain to Otoroshi \n \n### Configure Otoroshi with your domain\n\nYou can use ENVs or you can use a custom otoroshi.conf in your Docker container.\n\nFor the second option your otoroshi.conf would look like this :\n\n``` \n include \"application.conf\"\n http.port = 8080\n app {\n env = \"prod\"\n domain = \"mysubdomain.oto.tools\"\n rootScheme = \"https\"\n snowflake {\n seed = 0\n }\n events {\n maxSize = 1000\n }\n backoffice {\n subdomain = \"otoroshi\"\n session {\n exp = 86400000\n }\n }\n \n storage = \"redis\"\n redis {\n host=\"myredishost\"\n port=myredisport\n }\n \n privateapps {\n subdomain = \"privateapps\"\n }\n \n adminapi {\n targetSubdomain = \"otoroshi-admin-internal-api\"\n exposedSubdomain = \"otoroshi-api\"\n defaultValues {\n backOfficeGroupId = \"admin-api-group\"\n backOfficeApiKeyClientId = \"admin-client-id\"\n backOfficeApiKeyClientSecret = \"admin-client-secret\"\n backOfficeServiceId = \"admin-api-service\"\n }\n proxy {\n https = true\n local = false\n }\n }\n claim {\n sharedKey = \"myclaimsharedkey\"\n }\n }\n \n play.http {\n session {\n secure = false\n httpOnly = true\n maxAge = 2147483646\n domain = \".mysubdomain.oto.tools\"\n cookieName = \"oto-sess\"\n }\n }\n``` \n\n### Create a wildcard SSL certificate for your domain\n\nGo to [AWS Certificate Manager](https://eu-west-3.console.aws.amazon.com/acm/home?region=eu-west-3#/firstrun).\n\nBelow **Provision certificates** hit **Get started**.\n\n@@@ div { .centered-img }\n\n@@@ \n \nKeep the default selected value **Request a public certificate** and hit **Request a certificate**.\n \n@@@ div { .centered-img }\n\n@@@ \n\nPut your **Domain name**, use *. for wildcard, for instance *\\*.mysubdomain.oto.tools*, then hit **Next**.\n\n@@@ div { .centered-img }\n\n@@@ \n\nYou can choose between **Email validation** and **DNS validation**, I'd recommend **DNS validation**, then hit **Review**. \n \n@@@ div { .centered-img }\n\n@@@ \n \nVerify that you did put the right **Domain name** then hit **Confirm and request**. \n\n@@@ div { .centered-img }\n\n@@@\n \nAs you see in the image above, to let Amazon do the validation you have to add the `CNAME` record to your DNS configuration. Normally this operation takes around one day.\n \n### Configure Otoroshi AWS Elastic Beanstalk instance with the SSL certificate \n\nOnce the certificate is validated, you need to modify the configuration of Otoroshi-env to add the SSL certificate for HTTPS. \nFor that you need to go to [AWS Elastic Beanstalk applications](https://eu-west-3.console.aws.amazon.com/elasticbeanstalk/home?region=eu-west-3#/applications),\nhit **Otoroshi-env**, then on the left side hit **Configuration**, then on the **Load balancer** card hit **Modify**.\n\n@@@ div { .centered-img }\n\n@@@\n\nIn the **Application Load Balancer** section hit **Add listener**.\n\n@@@ div { .centered-img }\n\n@@@\n\nFill the popup as the image above, then hit **Add**. \n\nYou should now be seeing something like this : \n \n@@@ div { .centered-img }\n\n@@@ \n \n \nMake sure that your listener is enabled, and on the bottom right of the page hit **Apply**.\n\nNow you have **https**, so let's use Otoroshi.\n\n### Configure your DNS to redirect all traffic on your domain to Otoroshi\n \nIt's actually pretty simple, you just need to add a `CNAME` record to your DNS configuration, that redirects *\\*.mysubdomain.oto.tools* to the DNS name of Otoroshi's load balancer.\n\nTo find the DNS name of Otoroshi's load balancer go to [AWS Ec2](https://eu-west-3.console.aws.amazon.com/ec2/v2/home?region=eu-west-3#LoadBalancers:tag:elasticbeanstalk:environment-name=Otoroshi-env;sort=loadBalancerName)\n\nYou would find something like this : \n \n@@@ div { .centered-img }\n\n@@@ \n\nThere is your DNS name, so add your `CNAME` record. \n \nOnce all these steps are done, the AWS Elastic Beanstalk Otoroshi instance, would now be handling all the requests on your domain. ;) \n"},{"name":"clevercloud.md","id":"/deploy/clevercloud.md","url":"/deploy/clevercloud.html","title":"Clever Cloud","content":"# Clever Cloud\n\nNow you want to use Otoroshi on Clever Cloud. Otoroshi has been designed and created to run on Clever Cloud and a lot of choices were made because of how Clever Cloud works.\n\n## Create an Otoroshi instance on CleverCloud\n\nFirst, fork our project template on Github at https://github.com/MAIF/otoroshi-jar-clevercloud-template.\n\nIf you want to customize the build script, edit `./clevercloud/build.sh`\n\nIf you want to customize the configuration @ref:[use env. variables](../firstrun/env.md), you can use [the example provided below](#example-of-clevercloud-env-variables)\n\nCreate a new CleverCloud app based on your fork.\n\n@@@ div { .centered-img }\n\n@@@\n\nThen choose what kind of app your want to create, for Otoroshi, choose `Java + Jar`\n\n@@@ div { .centered-img }\n\n@@@\n\nNext, set up choose instance size and auto-scalling. Otoroshi can run on small instances, especially if you just want to test it.\n\n@@@ div { .centered-img }\n\n@@@\n\nFinally, choose a name for your app\n\n@@@ div { .centered-img }\n\n@@@\n\nNow you just need to customize environnment variables and add the custom build script as pre buid hook :\n\n`CC_PRE_BUILD_HOOK=./clevercloud/build.sh`\n\nat this point, you can also add other env. variables to configure Otoroshi like in [the example provided below](#example-of-clevercloud-env-variables)\n\n@@@ div { .centered-img }\n\n@@@\n\nYou can also use expert mode :\n\n@@@ div { .centered-img }\n\n@@@\n\nNow, your app is ready, don't forget to add a custom domains name on the CleverCloud app matching the Otoroshi app domain. For instance if you used domain names in env. variables like `changeme`, `changeme-admin-internal-api`, `changeme-api` on the `cleverapps.io` domain, declare `changeme.cleverapps.io`, `changeme-api.cleverapps.io`, `changeme-admin-internal-api.cleverapps.io`.\n\nYou will find the login/password tuple for first login in the app. logs.\n\n## Build and deploy Otoroshi from its source code\n\nFirst, fork our project template on Github at https://github.com/MAIF/otoroshi-clevercloud-template.\n\nIf you want to customize the build script, edit `./clevercloud/build.sh`\n\nIf you want to customize the configuration file, edit `./clevercloud/prod.conf` or @ref:[use env. variables](../firstrun/env.md)\n\nCreate a new Clever Cloud app based on your fork.\n\n@@@ div { .centered-img }\n\n@@@\n\nThen, you need to choose what kind of app your want to create, for Otoroshi, choose `Java or Scala + Play 2`\n\n@@@ div { .centered-img }\n\n@@@\n\nThen, you will be asked to choose what kind of machine you want to use. `M` instances are a good choice but you can use a less powerful ones. You can also activate auto-scaling or multi-instances to provie high availibility.\n\n@@@ div { .centered-img }\n\n@@@\n\nThen choose a name for your app :\n\n@@@ div { .centered-img }\n\n@@@\n\nNow you just need to customize environnment variables and add the custom build script as pre build hook :\n\n`CC_PRE_BUILD_HOOK=./clevercloud/build.sh`\n\nat this point, you can also add other env. variables to configure Otoroshi like in [the example provided below](#example-of-clevercloud-env-variables)\n\n@@@ div { .centered-img }\n\n@@@\n\nYou can also use expert mode :\n\n@@@ div { .centered-img }\n\n@@@\n\nNow, your app is ready, don't forget to add a custom domains name on the CleverCloud app matching the Otoroshi app domain. For instance if you used domain names in env. variables like `changeme`, `changeme-admin-internal-api`, `changeme-api` on the `cleverapps.io` domain, declare `changeme.cleverapps.io`, `changeme-api.cleverapps.io`, `changeme-admin-internal-api.cleverapps.io`.\n\nYou will find the login/password tuple for first login in the app. logs.\n\n## Example of CleverCloud env. variables\n\nYou can add more env variables to customize your Otoroshi instance like the following. Use the expert mode to copy/paste all the values in one shot :\n\n```\nAPP_ENV=prod\nAPP_STORAGE=inmemory\nAPP_DOMAIN=cleverapps.io\nAPP_ROOT_SCHEME=https\nAPP_BACKOFFICE_SUBDOMAIN=changeme\nADMIN_API_TARGET_SUBDOMAIN=changeme-admin-internal-api\nADMIN_API_EXPOSED_SUBDOMAIN=changeme-api\nADMIN_API_GROUP=psIZ0hI6eAQ2vp7DQoFfdUSfdmamtlkbXwYCe9WQHGBZMO6o5Kn1r2VVSmI61IVX\nADMIN_API_CLIENT_ID=pWkwudAifrflg8Bh\nADMIN_API_CLIENT_SECRET=ip53UuY5BFiM3wXkVUhhYrVdbsDYsANCNdRMnW3pU4I268ylsF6xxkvusS6Wv4AW\nADMIN_API_SERVICE_ID=GVQUWMZHaEYr1tCTNe9CdXOVE4DQnu1VUAx7YyXDlo5XupY3laZlWUnGyDt1vfGx\nCACHE_DEPENDENCIES=true\nCC_PRE_BUILD_HOOK=./clevercloud/build.sh\nCLAIM_SHAREDKEY=Tx1uQXW11pLNlZ25S4A08Uf8HbWDPxZ3KGSSm0B1s90gRk10PNy4d1HKY4Dnvvv5\nENABLE_METRICS=true\nJAVA_VERSION=8\nPORT=8080\nPLAY_CRYPTO_SECRET=7rNFga4AComd6ey09W9PaHqllLmPHb8WHBhlRe9xjTHOPlN15BCeSQf610cmLU1w\nSESSION_SECURE_ONLY=true\nSESSION_MAX_AGE=259200000\nSESSION_DOMAIN=changeme.cleverapps.io\nSESSION_NAME=otoroshi-session\nUSER_AGENT=otoroshi\n```\n"},{"name":"index.md","id":"/deploy/index.md","url":"/deploy/index.html","title":"Deploy to production","content":"# Deploy to production\n\nNow it's time to deploy Otoroshi in production, in this chapter we will see what kind of things you can do.\n\n@@@ index\n\n* [Clever Cloud](./clevercloud.md)\n* [AWS - Elastic Beanstalk](./aws-beanstalk.md)\n* [others](./other.md) \n* [Scaling](./scaling.md) \n\n@@@"},{"name":"other.md","id":"/deploy/other.md","url":"/deploy/other.html","title":"Others","content":"# Others\n\nOtoroshi can run wherever you want, even on a raspberry pi (Cluster^^) ;)\n\nThis section is not finished yet. So, as Otoroshi is available as a @ref:[Docker image](../getotoroshi/fromdocker.md) that you can run on any Docker compatible cloud, just go ahead and use it on cloud provider until we have more detailed documentation.\n\n## Running Otoroshi on AWS Elastic Beanstalk\n\nSee the @ref:[dedicated page to run Otoroshi on AWS Elastic Beanstalk](./aws-beanstalk.md)\n\n## Running Otoroshi on Amazon Elastic Container Service\n\nDeploy the @ref:[Docker image](../firstrun/run.md#from-docker) using [Amazon ECS](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html)\n\n## Running Otoroshi on GCE\n\nDeploy the @ref:[Docker image](../firstrun/run.md#from-docker) using [Google Compute Engine container integration](https://cloud.google.com/compute/docs/containers/deploying-containers)\n\n## Running Otoroshi on Azure\n\nDeploy the @ref:[Docker image](../firstrun/run.md#from-docker) using [Azure Container Service](https://azure.microsoft.com/en-us/services/container-service/)\n\n## Running Otoroshi on Heroku\n\nDeploy the @ref:[Docker image](../firstrun/run.md#from-docker) using [Docker integration](https://devcenter.heroku.com/articles/container-registry-and-runtime)\n\n## Running Otoroshi on CloudFoundry\n\nDeploy the @ref:[Docker image](../firstrun/run.md#from-docker) using [Docker integration](https://docs.cloudfoundry.org/adminguide/docker.html)\n\n## Running Otoroshi on your own infrastructure\n\nAs Otoroshi is a [Play Framework](https://www.playframework.com) application, you can read the doc about putting a `Play` app in production.\n\nhttps://www.playframework.com/documentation/2.6.x/ProductionConfiguration\n\nDownload the latest @ref:[Otoroshi distribution](../getotoroshi/frombinaries.md), unzip it, customize it and run it.\n"},{"name":"scaling.md","id":"/deploy/scaling.md","url":"/deploy/scaling.html","title":"Scaling Otoroshi","content":"# Scaling Otoroshi\n\n## Using multiple instances with a front load balancer\n\nOtoroshi has been designed to work with multiple instances. If you already have an infrastructure using frontal load balancing, you just have to declare Otoroshi instances as the target of all domain names handled by Otoroshi\n\n## Using master / workers mode of Otoroshi\n\nYou can read everything about it in @ref:[the clustering section](../topics/clustering.md) of the documentation.\n\n## Using IPVS\n\nYou can use [IPVS](https://en.wikipedia.org/wiki/IP_Virtual_Server) to load balance layer 4 traffic directly from the Linux Kernel to multiple instances of Otoroshi. You can find example of configuration [here](http://www.linuxvirtualserver.org/VS-DRouting.html) \n\n## Using DNS Round Robin\n\nYou can use [DNS round robin technique](https://en.wikipedia.org/wiki/Round-robin_DNS) to declare multiple A records under the domain names handled by Otoroshi.\n\n## Using software L4/L7 load balancers\n\nYou can use software L4 load balancers like NGINX or HAProxy to load balance layer 4 traffic directly from the Linux Kernel to multiple instances of Otoroshi.\n\nNGINX L7\n: @@snip [nginx-http.conf](../snippets/nginx-http.conf) \n\nNGINX L4\n: @@snip [nginx-tcp.conf](../snippets/nginx-tcp.conf) \n\nHA Proxy L7\n: @@snip [haproxy-http.conf](../snippets/haproxy-http.conf) \n\nHA Proxy L4\n: @@snip [haproxy-tcp.conf](../snippets/haproxy-tcp.conf) \n\n## Using a custom TCP load balancer\n\nYou can also use any other TCP load balancer, from a hardware box to a small js file like\n\ntcp-proxy.js\n: @@snip [tcp-proxy.js](../snippets/tcp-proxy.js) \n\ntcp-proxy.rs\n: @@snip [tcp-proxy.rs](../snippets/proxy.rs) \n\n"},{"name":"embedding.md","id":"/embedding.md","url":"/embedding.html","title":"Embedding Otoroshi","content":"# Embedding Otoroshi\n\nOtoroshi provides an API to start Otoroshi instances programmatically from any JVM app.\n\n## Getting the dependencies\n\nYou can get the Otoroshi dependency from bintray\n\nSbt\n: @@snip [build.sbt](./snippets/build.sbt)\n\nGradle\n: @@snip [build.gradle](./snippets/build.gradle)\n\n## Starting Otoroshi\n\nNow just instanciate an Otoroshi proxy with the configuration you like and you will be able to control it using the internal APIs of Otoroshi.\n\nScala\n: @@snip [embed.scala](./snippets/embed.scala)\n\nJava\n: @@snip [embed.java](./snippets/embed.java)\n"},{"name":"features.md","id":"/features.md","url":"/features.html","title":"Features ","content":"# Features \n\nAll the features supported by **Otoroshi** are listed below\n\n* Dynamic changes at runtime without full reload \n* Can proxy any HTTP/HTTP2 server\n* Can proxy websockets\n* Full featured admin Rest Api to control Otoroshi the way you want. Included, Swagger descriptor\n* Gorgeous React Web UI\n* Full end-to-end streaming of HTTP requests and responses\n* Completely non blocking and async internals\n* @ref:[Official Docker image](./getotoroshi/fromdocker.md)\n* @ref:[Multi backend datastore support](./firstrun/datastore.md)\n * Redis\n * In memory\n * Cassandra (experimental support)\n * Mongo (experimental support)\n * LevelDB (not suitable for production usage)\t\t\n* @ref:[Service is private (Api key access) by default with exclusions](./usage/2-services.md)\n* @ref:[Support wildcard domain names per service](./usage/2-services.md)\n* @ref:[Support routing headers for a service (ie. for service versioning)](./usage/2-services.md#service-meta)\n* @ref:[Support adding headers for a service request (ie. add Authorization header)](./usage/2-services.md#service-meta)\n* @ref:[Support custom html errors templates per service](./usage/2-services.md#custom-error-templates)\n* @ref:[Configurable circuit breaker and retries (with backoff) on network errors per service](./usage/2-services.md#service-circuit-breaker)\n* @ref:[Round Robin load balancing per service](./usage/2-services.md#targets)\n* @ref:[Services can be declared private (private apps) using an Auth0 domain](./usage/2-services.md#service-flags)\n* @ref:[Support for canary mode per service](./usage/2-services.md#canary-mode)\n* @ref:[Configurable health check per service](./usage/2-services.md#service-health-check)\n* @ref:[Support IP addresses blacklist per service (with wildcard support)](./usage/2-services.md#service-settings)\n* @ref:[Support IP addresses whitelist per service (with wildcard support)](./usage/2-services.md#service-settings)\n* @ref:[Support mutiple Api keys per service](./usage/3-apikeys.md)\n* @ref:[Support configurable throttling quota per Api key](./usage/3-apikeys.md#quotas)\n* @ref:[Support configurable daily quota per Api key](./usage/3-apikeys.md#quotas)\n* @ref:[Support configurable monthly quota per Api key](./usage/3-apikeys.md#quotas)\n* @ref:[Api keys are authorized for a group of services](./usage/3-apikeys.md)\n* @ref:[Api keys can be passed with custom headers, `Authorization: Basic ` headers, `Authorization: Bearer` or Cookies](./usage/3-apikeys.md)\n* @ref:[Add current Api key quotas usage in response headers](./usage/3-apikeys.md#quotas)\n* @ref:[Add current latencies in response headers](./usage/3-apikeys.md#quotas)\n* @ref:[Support service duplication through web UI](./usage/2-services.md#service-flags)\n* @ref:[Maintenance page per service](./usage/2-services.md#service-flags)\n* @ref:[Build page per service](./usage/2-services.md#service-flags)\n* @ref:[Force HTTPS usage per service](./usage/2-services.md#service-flags)\n* @ref:[Force secured exchanges with exclusions per service](./usage/2-services.md#service-flags)\n* @ref:[OpenAPI documentation displayed through web UI per service](./usage/2-services.md#service-settings)\n* @ref:[Live metrics per service (Rest)](./usage/4-monitor.md#service-live-stats)\n* @ref:[Metrics and analytics per service (if used with the Elastic connector)](./usage/4-monitor.md#service-analytics)\n* @ref:[Global metrics and analytics (if used with the Elastic connector)](./usage/7-metrics.md)\n* @ref:[Global audit log on admins actions](./usage/6-audit.md#audit-trail)\n* @ref:[Global alert log on admins actions](./usage/6-audit.md#alerts)\n* @ref:[Internal events can be sent outside using webhooks or Kafka topic](./setup/dangerzone.md#analytics-settings)\n* @ref:[Audit events can be sent outside using webhooks or Kafka topic](./setup/dangerzone.md#analytics-settings)\n* @ref:[Alerts events can be sent outside using webhooks or Kafka topic](./setup/dangerzone.md#analytics-settings)\n* @ref:[Alerts can be send to people by email using Mailgun](./integrations/mailgun.md)\n* @ref:[Support global IP addresses blacklist (with wildcard support)](./setup/dangerzone.md#whitelist-blacklist-settings)\n* @ref:[Support global IP addresses whitelist (with wildcard support)](./setup/dangerzone.md#whitelist-blacklist-settings)\n* @ref:[Support global endless responses for IP addresses (128 Gb of 0 for each response)](./setup/dangerzone.md#whitelist-blacklist-settings)\n* @ref:[Support global throttling quota](./setup/dangerzone.md#global-throttling-settings)\n* @ref:[Support global throttling quota per IP address (with wildcard support)](./setup/dangerzone.md#global-throttling-settings)\n* @ref:[Support global max number of concurrent connections](./setup/dangerzone.md#commons-settings)\n* Support global request tracing\n* @ref:[Support full JSON export of the reverse proxy state](./usage/8-importsexports.md#full-export)\n* @ref:[Support full JSON import of the reverse proxy state](./usage/8-importsexports.md#full-import)\n* @ref:[Support initial internal state import from JSON local file](./firstrun/configfile.md#db-configuration)\n* @ref:[Support initial internal state import from JSON file over HTTP](./firstrun/configfile.md#db-configuration)\n* @ref:[Enforce incoming JWT token verification and transformation](./topics/jwt-verifications.md)\n* @ref:[Introduce HTTP level chaos engineering pratices with the Snow Monkey](./topics/snow-monkey.md)\n* @ref:[Native support for service mesh architectures](./topics/service-mesh.md)\n* @ref:[Global live metrics](./setup/index.md#first-login)\n* @ref:[Send metrics to a StatsD/Datadog agent](./integrations/statsd.md)\n* @ref:[Advanced CleverCloud integration (create services from CleverCloud apps)](./integrations/clevercloud.md)\n* @ref:[Support admin login with any auth. module](./usage/9-auth.md)\n* @ref:[Support admin login with FIDO U2F device](./setup/admin.md#create-admin-user-with-u2f-device-login)\n* @ref:[Dynamic SSL termination](./topics/ssl.md)\n* @ref:[Rust CLI running on MacOS, Linux and Windows](./cli.md)\n* @ref:[Connectors to various HTTP services providers](./connectors/index.md)\n * [generic connector API](https://github.com/MAIF/otoroshi/tree/master/connectors/common)\n * @ref:[Clever Cloud](./connectors/clevercloud.md)\n * @ref:[Rancher](./connectors/rancher.md)\n * @ref:[Kubernetes](./connectors/kubernetes.md)\n"},{"name":"configfile.md","id":"/firstrun/configfile.md","url":"/firstrun/configfile.html","title":"Config. with files","content":"# Config. with files\n\nThere is a lot of things you can configure in Otoroshi. By default, Otoroshi provides a configuration that should be enough for testing purpose. But you'll likely need to update this configuration when you'll need to move into production.\n\nIn this page, any configuration property can be set at runtime using a `-D` flag when launching Otoroshi like\n\n```sh\njava -Dhttp.port=8080 -jar otoroshi.jar\n```\n\nor\n\n```sh\n./bin/otoroshi -Dhttp.port=8080 \n```\n\n## Common configuration\n\n| name | type | default value | description |\n| ---- |:----:| -------------- | ----- |\n| `app.domain` | string | \"oto.tools\" | the domain on which Otoroshi UI/API is be exposed|\n| `app.rootScheme` | string | \"http\" | the scheme on which Otoroshi is exposed, either \"http\" or \"https\" |\n| `app.snowflake.seed` | number | 0 | this number will is used to generate unique ids across the cluster. Each Otorshi instance must have a unique seed. |\n| `app.events.maxSize` | number | 1000 | max number of analytic and alert events stored locally |\n| `app.backoffice.exposed` | boolean | true | does the current Otoroshi instance exposed a backoffice ui|\n| `app.backoffice.subdomain` | string | \"otoroshi\" | the subdomain on wich Otoroshi backoffice will be served |\n| `app.backoffice.session.exp` | number | 86400000 | the number of seconds before the Otoroshi backoffice session expires |\n| `app.privateapps.subdomain` | string | \"privateapps\" | the subdomain on which private apps UI are served |\n| `app.privateapps.session.exp` | number | 86400000 | the number of seconds before the private apps session expires |\n| `app.claim.sharedKey` | string | \"secret\" | the shared secret used for signing the JWT token passed between Otoroshi and backend services |\n| `app.webhooks.size` | number | 100 | number of events sent at most when calling one of the analytics webhooks |\n| `app.throttlingWindow` | number | 10 | time window (in seconds) used to compute throttling quotas for ApiKeys |\n\n## Admin API configuration\n\nWhen Otoroshi starts for the first time, its datastore is empty. As Otoroshi uses Otoroshi to expose its admin REST API, you'll have to provide the details for the admin API exposition. **This part is super important** because if you go to production with the default values, your Otoroshi server won't be secured anymore.\n\n@@@ warning\nYOU HAVE TO CUSTOMIZE THE FOLLOWING VALUES BEFORE GOING TO PRODUCTION !!\n@@@\n\nSome of the following terms will seem obscure to you, but you will learn their meaning in the following chapters :)\n\n| name | type | default value | description |\n| ---- |:----:| -------------- | ----- |\n| `app.adminapi.exposed` | boolean | true | does the current Otoroshi instance expose an admin API |\n| `app.adminapi.targetSubdomain` | string | \"otoroshi-admin-internal-api\" | the subdomain on wich admin API call will be redirected from `app.adminapi.exposedSubdomain` |\n| `app.adminapi.exposedSubdomain` | string | \"otoroshi-api\" | the subdomain on wich the Otoroshi admin API will be exposed |\n| `app.adminapi.defaultValues.backOfficeGroupId` | string | \"admin-api-group\" | the name of the service groups that will contain the service descriptors for the Otoroshi admin API |\n| `app.adminapi.defaultValues.backOfficeApiKeyClientId` | string | \"admin-api-apikey-id\" | the client id of the Otoroshi admin API apikey |\n| `app.adminapi.defaultValues.backOfficeApiKeyClientSecret` | string | \"admin-api-apikey-secret\" | the client secret of the Otoroshi admin API apikey |\n| `app.adminapi.defaultValues.backOfficeServiceId` | string | \"admin-api-service\" | the id of the service descriptors for the Otoroshi admin API |\n| `app.adminapi.proxy.https` | boolean | false | whether or not the current Otoroshi instance serves its content over https. This setting is useful for the backoffice UI to access Otoroshi admin API |\n| `app.adminapi.proxy.local` | boolean | true | whether or not the admin API is accessible through `127.0.0.1`. This setting is useful for the backoffice UI to access Otoroshi admin API |\n\n## DB configuration\n\nAs Otoroshi supports multiple datastores, you'll have to provide some details about how to connect/configure it.\n\n| name | type | default value | description |\n| ---- |:----:| -------------- | ----- |\n| `app.storage` | string | \"inmemory\" | what kind of storage engine you want to use. Possible values are `inmemory`, `leveldb`, `redis`, `cassandra`, `mongo` |\n| `app.importFrom` | string | | a file path or a URL to an Otoroshi export file. If the datastore is empty on startup, this file will be used to import data to the empty DB |\n| `app.importFromHeaders` | array | [] | a list of `:` separated header to use if the `app.importFrom` setting is a URL |\n| `app.initialData` | object |  | object representing Otoroshi internal data as exported from danger zone so you don't need a config file and a data import file |\n| `app.redis.host` | string | \"localhost\" | the host of the redis server |\n| `app.redis.port` | number | 6379 | the port of the redis server |\n| `app.redis.slaves` | array | [] | the redis slaves lists |\n| `app.leveldb.path` | string | \"./leveldb\" | the path where levelDB files will be written |\n| `app.cassandra.hosts` | string | \"127.0.0.1\" | the host of the cassandra server |\n| `app.cassandra.host` | string | \"127.0.0.1\" | the list of cassandra hosts |\n| `app.cassandra.port` | number | 9042 | the port of the cassandra servers |\n| `app.mongo.uri` | string | \"mongodb://localhost:27017/default\" | the mongo URI following Mongo semantic https://docs.mongodb.com/manual/reference/connection-string/ |\n\n## Headers configuration\n\nOtoroshi uses a fair amount of http headers in order to work properly. The name of those headers are customizable to fit your needs.\n\n| name | type | default value | description |\n| ---- |:----:| -------------- | ----- |\n| `otoroshi.headers.trace.label` | string | \"Otoroshi-Viz-From-Label\" | header to pass request tracing informations |\n| `otoroshi.headers.trace.from` | string | \"Otoroshi-Viz-From\" | header to pass request tracing informations (ip address) |\n| `otoroshi.headers.trace.parent` | string | \"Otoroshi-Parent-Request\" | header to pass request tracing informations (parent request id) |\n| `otoroshi.headers.request.adminprofile` | string | \"Otoroshi-Admin-Profile\" | header to pass admin name when the admin API is called from the Otoroshi backoffice |\n| `otoroshi.headers.request.clientid` | string | \"Otoroshi-Client-Id\" | header to pass apikey client id |\n| `otoroshi.headers.request.clientsecret` | string | \"Otoroshi-Client-Secret\" | header to pass apikey client secret |\n| `otoroshi.headers.request.id` | string | \"Otoroshi-Request-Id\" | header containing the id of the current request |\n| `otoroshi.headers.response.proxyhost` | string | \"Otoroshi-Proxied-Host\" | header containing the proxied host |\n| `otoroshi.headers.response.error` | string | \"Otoroshi-Error\" | header containing whether or not the request generated an error |\n| `otoroshi.headers.response.errormsg` | string | \"Otoroshi-Error-Msg\" | header containing error message if some |\n| `otoroshi.headers.response.proxylatency` | string | \"Otoroshi-Proxy-Latency\" | header containing the current latency induced by Otoroshi |\n| `otoroshi.headers.response.upstreamlatency` | string | \"Otoroshi-Upstream-Latency\" | header containing the current latency from Otoroshi to service backend |\n| `otoroshi.headers.response.dailyquota` | string | \"Otoroshi-Daily-Calls-Remaining\" | header containing the number of remaining daily call (apikey) |\n| `otoroshi.headers.response.monthlyquota` | string | \"Otoroshi-Monthly-Calls-Remaining\" | header containing the number of remaining monthly call (apikey) |\n| `otoroshi.headers.comm.state` | string | \"Otoroshi-State\" | header containing a random value for secured mode |\n| `otoroshi.headers.comm.stateresp` | string | \"Otoroshi-State-Resp\" | header containing a random value for secured mode |\n| `otoroshi.headers.comm.claim` | string | \"Otoroshi-Claim\" | header containing a JWT token for secured mode |\n| `otoroshi.headers.healthcheck.test` | string | \"Otoroshi-Health-Check-Logic-Test\" | header containing a logic test for healthcheck |\n| `otoroshi.headers.healthcheck.testresult` | string | \"Otoroshi-Health-Check-Logic-Test-Result\" | header containing the result of a logic test for healthcheck |\n| `otoroshi.headers.jwt.issuer` | string | \"Otoroshi\" | the name of the issuer for the JWT token |\n| `otoroshi.headers.canary.tracker` | string | \"Otoroshi-Canary-Id\" | header containing the ID of the canary session if enabled |\n\n## Play specific configuration\n\nAs Otoroshi is a [Play app](https://www.playframework.com/), you should take a look at [Play configuration documentation](https://www.playframework.com/documentation/2.6.x/Configuration) to tune its internal configuration\n\n| name | type | default value | description |\n| ---- |:----:| -------------- | ----- |\n| `http.port` | number | 8080 | the http port used by Otoroshi. You can use 'disabled' as value if you don't want to use http |\n| `https.port` | number | disabled | the https port used by Otoroshi. You can use 'disabled' as value if you don't want to use https |\n| `http2.enabled` | boolean | false | whether or not http2 is enabled on the Otoroshi server. You need to configure https (listed bellow) to be able to use it |\n| `play.http.secret.key` | string | \"secret\" | the secret used to sign Otoroshi session cookie |\n| `play.http.session.secure` | boolean | false | whether or not the Otoroshi backoffice session will be served over https only |\n| `play.http.session.httpOnly` | boolean | true | whether or not the Otoroshi backoffice session will be accessible from Javascript |\n| `play.http.session.maxAge` | number | 259200000 | the number of seconds before Otoroshi backoffice session expired |\n| `play.http.session.domain` | string | \".oto.tools\" | the domain on which the Otoroshi backoffice session is authorized |\n| `play.http.session.cookieName` | string | \"otoroshi-session\" | the name of the Otoroshi backoffice session |\n| `play.ws.play.ws.useragent` | string | \"Otoroshi\" | the user agent sent by Otoroshi if not present on the original http request |\n| `play.server.https.keyStore.path` | string | | the path to the keystore containing the private key and certificate, if not provided generates a keystore for you |\n| `play.server.https.keyStore.type` | string | JKS | the key store type, defaults to JKS |\n| `play.server.https.keyStore.password` | string | '' | the password, defaults to a blank password |\n| `play.server.https.keyStore.algorithm` | string | | the key store algorithm, defaults to the platforms default algorithm |\n\n## More config. options\n\nSee https://github.com/MAIF/otoroshi/blob/master/otoroshi/conf/base.conf and https://github.com/MAIF/otoroshi/blob/master/otoroshi/conf/application.conf\n\nif you want to configure https on your Otoroshi server, just read [PlayFramework documentation about it](https://www.playframework.com/documentation/2.6.x/ConfiguringHttps)\n\n## Example of configuration file\n\n```conf\ninclude \"application.conf\"\n\nhttp.port = 8080\n\napp {\n storage = \"leveldb\"\n importFrom = \"./my-state.json\"\n env = \"prod\"\n domain = \"oto.tools\"\n rootScheme = \"http\"\n snowflake {\n seed = 0\n }\n events {\n maxSize = 1000\n }\n backoffice {\n subdomain = \"otoroshi\"\n session {\n exp = 86400000\n }\n }\n privateapps {\n subdomain = \"privateapps\"\n session {\n exp = 86400000\n }\n }\n adminapi {\n targetSubdomain = \"otoroshi-admin-internal-api\"\n exposedSubdomain = \"otoroshi-api\"\n defaultValues {\n backOfficeGroupId = \"admin-api-group\"\n backOfficeApiKeyClientId = \"admin-api-apikey-id\"\n backOfficeApiKeyClientSecret = \"admin-api-apikey-secret\"\n backOfficeServiceId = \"admin-api-service\"\n }\n }\n claim {\n sharedKey = \"mysecret\"\n }\n leveldb {\n path = \"./leveldb\"\n }\n}\n\nplay.http {\n session {\n secure = false\n httpOnly = true\n maxAge = 2592000000\n domain = \".oto.tools\"\n cookieName = \"oto-sess\"\n }\n}\n```\n"},{"name":"datastore.md","id":"/firstrun/datastore.md","url":"/firstrun/datastore.html","title":"Choose your datastore","content":"# Choose your datastore\n\nRight now, Otoroshi supports multiple datastore.\n\nYou can choose one datastore over another depending on your use case.\n\nAvailable datastores are the following :\n\n* in memory\n* redis\n* cassandra (experimental support)\n* mongodb (experimental support)\n* levelDB (not suitable for production usage)\n\nThe **levelDB** datastore is pretty handy for testing purposes, but is not supposed to be used in production mode.\n\nThe **in-memory** datastore is kind of interesting... It can be used for testing purposes, but it is also a good candidate for production because of its fastness. But in that case, you need to provide a way to feed the deployed in-memory instances after the initial boot. In a future release (i.e. not yet :D), we will provide a master/workers slave based on Otoroshi in memory instances and kafka (see https://github.com/MAIF/otoroshi/issues/8 for more details about the feature).\n\nThe **redis** datastore is quite nice when you want to easily deploy several Otoroshi instances.\n\nIf you need a datastore more scalable than redis, then you can use the **cassandra** datastore or the **mongodb** datastore.\n\nWe plan to add more datastores support in the future :)\n\n@@@ div { .centered-img }\n\n@@@\n"},{"name":"env.md","id":"/firstrun/env.md","url":"/firstrun/env.html","title":"Config. with ENVs","content":"# Config. with ENVs\n\nNow that you know @ref:[how to configure Otoroshi with the config. file](./configfile.md) every property in the following block can be overriden by an environment variable (an env. variable is written like `${?ENV_VARIABLE}`).\n\n```\napp.storage = ${?APP_STORAGE}\napp.importFrom = ${?APP_IMPORT_FROM}\napp.domain = ${?APP_DOMAIN}\napp.rootScheme = ${?APP_ROOT_SCHEME}\napp.throttlingWindow = ${?THROTTLING_WINDOW}\napp.snowflake.seed = ${?INSTANCE_NUMBER}\napp.events.maxSize = ${?MAX_EVENTS_SIZE}\napp.backoffice.exposed = ${?APP_BACKOFFICE_EXPOSED}\napp.backoffice.subdomain = ${?APP_BACKOFFICE_SUBDOMAIN}\napp.backoffice.session.exp = ${?APP_BACKOFFICE_SESSION_EXP}\napp.privateapps.subdomain = ${?APP_PRIVATEAPPS_SUBDOMAIN}\napp.privateapps.session.exp = ${?APP_PRIVATEAPPS_SESSION_EXP}\napp.adminapi.exposed = ${?ADMIN_API_EXPOSED}\napp.adminapi.targetSubdomain = ${?ADMIN_API_TARGET_SUBDOMAIN}\napp.adminapi.exposedSubdomain = ${?ADMIN_API_EXPOSED_SUBDOMAIN}\napp.adminapi.defaultValues.backOfficeGroupId = ${?ADMIN_API_GROUP}\napp.adminapi.defaultValues.backOfficeApiKeyClientId = ${?ADMIN_API_CLIENT_ID}\napp.adminapi.defaultValues.backOfficeApiKeyClientSecret = ${?ADMIN_API_CLIENT_SECRET}\napp.adminapi.defaultValues.backOfficeServiceId = ${?ADMIN_API_SERVICE_ID}\napp.adminapi.proxy.https = ${?ADMIN_API_HTTPS}\napp.adminapi.proxy.local = ${?ADMIN_API_LOCAL}\napp.claim.sharedKey = ${?CLAIM_SHAREDKEY}\napp.webhooks.size = ${?WEBHOOK_SIZE}\napp.redis.host = ${?REDIS_HOST}\napp.redis.port = ${?REDIS_PORT}\napp.redis.password = ${?REDIS_PASSWORD}\napp.redis.windowSize = ${?REDIS_WINDOW_SIZE}\napp.redis.useScan = ${?REDIS_USE_SCAN}\napp.inmemory.windowSize = ${?INMEMORY_WINDOW_SIZE}\napp.leveldb.windowSize = ${?LEVELDB_WINDOW_SIZE}\napp.leveldb.path = ${?LEVELDB_PATH}\napp.cassandra.windowSize = ${?CASSANDRA_WINDOW_SIZE}\napp.cassandra.hosts = ${?CASSANDRA_HOSTS}\napp.cassandra.host = ${?CASSANDRA_HOST}\napp.cassandra.port = ${?CASSANDRA_PORT}\napp.elastic.url = ${?ELASTIC_URL}\napp.elastic.user = ${?ELASTIC_USER}\napp.elastic.password = ${?ELASTIC_PASSWORD}\napp.elastic.index = ${?ELASTIC_INDEX}\napp.elastic.type = ${?ELASTIC_TYPE}\napp.mongo.uri = ${?MONGO_URI}\nhttp.port = ${?PORT}\nhttps.port = ${?HTTPS_PORT}\nhttp2.enabled = ${?HTTP2_ENABLED}\nplay.http.secret.key = ${?PLAY_CRYPTO_SECRET}\nplay.http.session.secure = ${?SESSION_SECURE_ONLY}\nplay.http.session.maxAge = ${?SESSION_MAX_AGE}\nplay.http.session.domain = ${?SESSION_DOMAIN}\nplay.http.session.cookieName = ${?SESSION_NAME}\nplay.ws.play.ws.useragent=${?USER_AGENT}\n```\n"},{"name":"host.md","id":"/firstrun/host.md","url":"/firstrun/host.html","title":"Setup your hosts","content":"# Setup your hosts\n\nBy default, Otoroshi starts with domain `oto.tools` that targets `127.0.0.1`. Of course you can change the domain, you have to add the values in your `/etc/hosts` file according to the setting you put in Otoroshi configuration\n\n* `app.domain` => `oto.tools`\n* `app.backoffice.subdomain` => `otoroshi`\n* `app.privateapps.subdomain` => `privateapps`\n* `app.adminapi.exposedSubdomain` => `otoroshi-api`\n* `app.adminapi.targetSubdomain` => `otoroshi-admin-internal-api`\n\nfor instance if you want to change the default domain and use something like `otoroshi.mydomain.org`, then start otoroshi like \n\n```sh\njava -Dapp.domain=mydomain.org -jar otoroshi.jar\n```\n\n@@@ warning\nOtoroshi cannot be accessed using `http://127.0.0.1:8080` or `http://localhost:8080` because Otoroshi uses Otoroshi to serve it's own UI and API. When otoroshi starts with an empty database, it will create a service descriptor for that using `app.domain` and the settings listed on this page and in the * [Config. with files page](./configfile.md) that serve Otoroshi API and UI on `http://otoroshi-api.${app.domain}` and `http://otoroshi.${app.domain}`.\nOnce the descriptor is saved in database, if you want to change `app.domain`, you'll have to edit the descriptor in the database or restart Otoroshi with an empty database.\n@@@\n"},{"name":"index.md","id":"/firstrun/index.md","url":"/firstrun/index.html","title":"First run","content":"# First run\n\nNow that you have your own distro of Otoroshi, it's time to run it. \n\nBut before doing so, you'll have to make some choices about some essential stuff in order to have your own customized version of Otoroshi.\n\nLet's start with the datastore\n\n\n@@@ index\n\n* [choose a datastore](./datastore.md)\n* [use custom config file](./configfile.md)\n* [use ENV](./env.md)\n* [initial state](./initialstate.md)\n* [Hosts](./host.md)\n* [Run](./run.md)\n\n@@@"},{"name":"initialstate.md","id":"/firstrun/initialstate.md","url":"/firstrun/initialstate.html","title":"Import initial state","content":"# Import initial state\n\nNow you are almost ready to run Otoroshi for the first time, but maybe you want to import data from previous Otoroshi installation in your current datastore.\n\nTo do that, you need to add the `app.importFrom` setting to the Otoroshi configuration (of `$APP_IMPORT_FROM` env).\n\nIt can be a file path or a URL\n\n## Example of export\n\n```json\n{\n \"config\": {\n \"lines\": [\"prod\"], \n \"limitConcurrentRequests\": true,\n \"maxConcurrentRequests\": 500,\n \"useCircuitBreakers\": true,\n \"apiReadOnly\": false,\n \"registerFromCleverHook\": false,\n \"u2fLoginOnly\": true,\n \"ipFiltering\": {\n \"whitelist\": [],\n \"blacklist\": []\n },\n \"throttlingQuota\": 100000,\n \"perIpThrottlingQuota\": 500,\n \"analyticsEventsUrl\": null,\n \"analyticsWebhooks\": [],\n \"alertsWebhooks\": [],\n \"alertsEmails\": [],\n \"endlessIpAddresses\": []\n },\n \"admins\": [],\n \"simpleAdmins\": [\n {\n \"username\": \"admin@otoroshi.io\",\n \"password\": \"xxxxxxxxxxxxxxxxx\",\n \"label\": \"Otoroshi Admin\",\n \"createdAt\": 1493971715708\n }\n ],\n \"serviceGroups\": [\n {\n \"id\": \"default\",\n \"name\": \"default-group\",\n \"description\": \"The default group\"\n },\n {\n \"id\": \"admin-api-group\",\n \"name\": \"Otoroshi Admin Api group\",\n \"description\": \"No description\"\n }\n ],\n \"apiKeys\": [\n {\n \"clientId\": \"admin-api-apikey-id\",\n \"clientSecret\": \"admin-api-apikey-secret\",\n \"clientName\": \"Otoroshi Backoffice ApiKey\",\n \"authorizedGroup\": \"admin-api-group\",\n \"enabled\": true,\n \"throttlingQuota\": 10000000,\n \"dailyQuota\": 10000000,\n \"monthlyQuota\": 10000000,\n \"metadata\": {}\n }\n ],\n \"serviceDescriptors\": [\n {\n \"id\": \"admin-api-service\",\n \"groupId\": \"admin-api-group\",\n \"name\": \"otoroshi-admin-api\",\n \"env\": \"prod\",\n \"domain\": \"oto.tools\",\n \"subdomain\": \"otoroshi-api\",\n \"targets\": [\n {\n \"host\": \"localhost:8080\",\n \"scheme\": \"http\"\n }\n ],\n \"root\": \"/\",\n \"enabled\": true,\n \"privateApp\": false,\n \"forceHttps\": false,\n \"maintenanceMode\": false,\n \"buildMode\": false,\n \"enforceSecureCommunication\": true,\n \"publicPatterns\": [],\n \"privatePatterns\": [],\n \"additionalHeaders\": {\n \"Host\": \"otoroshi-admin-internal-api.oto.tools\"\n },\n \"matchingHeaders\": {},\n \"ipFiltering\": {\n \"whitelist\": [],\n \"blacklist\": []\n },\n \"api\": {\n \"exposeApi\": false\n },\n \"healthCheck\": {\n \"enabled\": false,\n \"url\": \"/\"\n },\n \"metadata\": {}\n }\n ],\n \"errorTemplates\": []\n}\n```\n"},{"name":"run.md","id":"/firstrun/run.md","url":"/firstrun/run.html","title":"Run Otoroshi","content":"# Run Otoroshi\n\nNow you are ready to run Otoroshi. You can run the following command with some tweaks depending on the way you want to configure Otoroshi. If you want to pass a custom configuration file, use the `-Dconfig.file=/path/to/file.conf` flag in the following commands.\n\n## From .zip file\n\n```sh\nunzip otoroshi-dist.zip\ncd otoroshi-vx.x.x\n./bin/otoroshi\n```\n\n## From .jar file\n\nFor Java 8 & Java 11\n\n```sh\njava -jar otoroshi.jar\n```\n\n## From docker\n\n```sh\ndocker run -p \"8080:8080\" maif/otoroshi:1.4.8-dev\n```\n\nYou can also pass useful args like :\n\n```\ndocker run -p \"8080:8080\" otoroshi -Dconfig.file=/usr/app/otoroshi/conf/otoroshi.conf -Dlogger.file=/usr/app/otoroshi/conf/otoroshi.xml\n```\n\nIf you want to provide your own config file, you can read @ref:[the documentation about config files](../firstrun/configfile.md).\n\nYou can also provide some ENV variable using the `--env` flag to customize your Otoroshi instance.\n\nThe list of possible env variables is available @ref:[here](../firstrun/env.md).\n\nYou can use a volume to provide configuration like :\n\n```sh\ndocker run -p \"8080:8080\" -v \"$(pwd):/usr/app/otoroshi/conf\" maif/otoroshi\n```\n\nYou can also use a volume if you choose to use `leveldb` datastore like :\n\n```sh\ndocker run -p \"8080:8080\" -v \"$(pwd)/leveldb:/usr/app/otoroshi/leveldb\" maif/otoroshi -Dapp.storage=leveldb\n```\n\nYou can also use a volume if you choose to use exports files :\n\n```sh\ndocker run -p \"8080:8080\" -v \"$(pwd):/usr/app/otoroshi/imports\" maif/otoroshi -Dapp.importFrom=/usr/app/otoroshi/imports/export.json\n```\n\n## Run examples\n\n```sh\n$ java \\\n -Xms2G \\\n -Xmx8G \\\n -Dhttp.port=8080 \\\n -Dapp.importFrom=/home/user/otoroshi.json \\\n -Dconfig.file=/home/user/otoroshi.conf \\\n -jar ./otoroshi.jar\n\n[warn] otoroshi-in-memory-datastores - Now using InMemory DataStores\n[warn] otoroshi-env - The main datastore seems to be empty, registering some basic services\n[warn] otoroshi-env - Importing from: /home/user/otoroshi.json\n[info] play.api.Play - Application started (Prod)\n[info] p.c.s.AkkaHttpServer - Listening for HTTP on /0:0:0:0:0:0:0:0:8080\n```\n\nIf you choose to start Otoroshi without importing existing data, Otoroshi will create a new admin user and print the login details in the log. When you will log into the admin dashboard, Otoroshi will ask you to create another account to avoid security issues.\n\n```sh\n$ java \\\n -Xms2G \\\n -Xmx8G \\\n -Dhttp.port=8080 \\\n -jar otoroshi.jar\n\n[warn] otoroshi-in-memory-datastores - Now using InMemory DataStores\n[warn] otoroshi-env - The main datastore seems to be empty, registering some basic services\n[warn] otoroshi-env - You can log into the Otoroshi admin console with the following credentials: admin@otoroshi.io / HHUsiF2UC3OPdmg0lGngEv3RrbIwWV5W\n[info] play.api.Play - Application started (Prod)\n[info] p.c.s.AkkaHttpServer - Listening for HTTP on /0:0:0:0:0:0:0:0:8080\n```\n"},{"name":"frombinaries.md","id":"/getotoroshi/frombinaries.md","url":"/getotoroshi/frombinaries.html","title":"From binaries","content":"# From binaries\n\nIf you want to download the last version of Otoroshi and its CLI, you can grab them from the release page of the Otoroshi github page :\n\nGo to https://github.com/MAIF/otoroshi/releases and get the last version of the `otoroshi-dist.zip` file or `otoroshi.jar` file\n"},{"name":"fromdocker.md","id":"/getotoroshi/fromdocker.md","url":"/getotoroshi/fromdocker.html","title":"From docker","content":"# From docker\n\nIf you're a Docker aficionado, Otoroshi is provided as a Docker image that your can pull directly from Official repos.\n\nfirst, fetch the last Docker image of Otoroshi :\n\n```sh\ndocker pull maif/otoroshi:1.4.18\n# or \ndocker pull maif/otoroshi:latest\n# or \ndocker pull maif/otoroshi:jdk8-1.4.18\n# or \ndocker pull maif/otoroshi:jdk11-1.4.18\n# or \ndocker pull maif/otoroshi:jdk12-1.4.18\n# or \ndocker pull maif/otoroshi:jdk13-1.4.18\n# or \ndocker pull maif/otoroshi:jdk14-1.4.18\n```"},{"name":"fromsources.md","id":"/getotoroshi/fromsources.md","url":"/getotoroshi/fromsources.html","title":"From sources","content":"# From sources\n\nto build Otoroshi from sources, you need the following tools :\n\n* git\n* JDK 8\n* SBT\n* node\n* yarn\n\nOnce you've installed all those tools, go to the [Otoroshi github page](https://github.com/MAIF/otoroshi) and clone the sources :\n\n```sh\ngit clone https://github.com/MAIF/otoroshi.git --depth=1\n```\n\nthen you need to run the `build.sh` script to build the documentation, the React UI and the server :\n\n```sh\nsh ./scripts/build.sh\n```\n\nand that's all, you can grab your Otoroshi package at `otoroshi/target/scala-2.12/otoroshi` or `otoroshi/target/universal/`.\n\nFor those who want to build only parts of Otoroshi, read the following.\n\n## Build the documentation only\n\nGo to the `documentation` folder and run :\n\n```sh\nsbt ';clean;paradox'\n```\n\nThe documentation is located at `documentation/target/paradox/site/main/`\n\n## Build the React UI\n\nGo to the `otoroshi/javascript` folder and run :\n\n```sh\nyarn install\nyarn build\n```\n\nYou will find the JS bundle at `otoroshi/public/javascripts/bundle/bundle.js`.\n\n## Build the Otoroshi server\n\nGo to the `otoroshi` folder and run :\n\n```sh\nsbt ';clean;compile;dist;assembly'\n```\n\nYou will find your Otoroshi package at `otoroshi/target/scala-2.12/otoroshi` or `otoroshi/target/universal/`.\n"},{"name":"index.md","id":"/getotoroshi/index.md","url":"/getotoroshi/index.html","title":"Get Otoroshi","content":"# Get Otoroshi\n\nThere are several ways to get Otoroshi to run it on your system.\n\nLet's start with a good old build from sources :)\n\n@@@ index\n\n* [from sources](./fromsources.md)\n* [from binaries](./frombinaries.md)\n* [from docker](./fromdocker.md)\n\n@@@"},{"name":"index.md","id":"/index.md","url":"/index.html","title":"Otoroshi","content":"# Otoroshi\n\n**Otoroshi** is a layer of lightweight api management on top of a modern http reverse proxy written in Scala and developped by the MAIF OSS team that can handle all the calls to and between your microservices without service locator and let you change configuration dynamicaly at runtime.\n\n\n> *The Otoroshi is a large hairy monster that tends to lurk on the top of the torii gate in front of Shinto shrines. It's a hostile creature, but also said to be the guardian of the shrine and is said to leap down from the top of the gate to devour those who approach the shrine for only self-serving purposes.*\n\n@@@ div { .centered-img }\n[![Build Status](https://travis-ci.org/MAIF/otoroshi.svg?branch=master)](https://travis-ci.org/MAIF/otoroshi) [![Join the chat at https://gitter.im/MAIF/otoroshi](https://badges.gitter.im/MAIF/otoroshi.svg)](https://gitter.im/MAIF/otoroshi?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [ ![Download](https://img.shields.io/github/release/MAIF/otoroshi.svg) ](hhttps://github.com/MAIF/otoroshi/releases/download/v1.4.18/otoroshi.jar)\n@@@\n\n@@@ div { .centered-img }\n\n@@@\n\n## Installation\n\nYou can download the latest build of Otoroshi as a [fat jar](https://github.com/MAIF/otoroshi/releases/download/v1.4.18/otoroshi.jar), as a [zip package](https://github.com/MAIF/otoroshi/releases/download/v1.4.18/otoroshi-dist.zip) or as a @ref:[docker image](./getotoroshi/fromdocker.md).\n\nYou can install and run Otoroshi with this little bash snippet\n\n```sh\ncurl -L -o otoroshi.jar 'https://github.com/MAIF/otoroshi/releases/download/v1.4.18/otoroshi.jar'\njava -jar otoroshi.jar\n```\n\nor using docker\n\n```sh\ndocker run -p \"8080:8080\" maif/otoroshi:1.4.18\n```\n\nnow open your browser to http://otoroshi.oto.tools:8080/, **log in with the credential generated in the logs** and explore by yourself, if you want better instructions, just go to the @ref:[Quick Start](./quickstart.md) or directly to the @ref:[installation instructions](./getotoroshi/index.md)\n\n## Documentation\n\n* @ref:[About Otoroshi](./about.md)\n* @ref:[Architecture](./archi.md)\n* @ref:[Features](./features.md)\n* @ref:[Try Otoroshi in 5 minutes](./quickstart.md)\n* @ref:[Video tutorials](./videos.md)\n* @ref:[Get Otoroshi](./getotoroshi/index.md)\n* @ref:[First run](./firstrun/index.md)\n* @ref:[Setup Otoroshi](./setup/index.md)\n* @ref:[Using Otoroshi](./usage/index.md)\n* @ref:[Third party Integrations](./integrations/index.md)\n* @ref:[Detailed topics](./topics/index.md)\n* @ref:[Embedding Otoroshi](./embedding.md)\n* @ref:[Admin REST API](./api.md)\n* @ref:[Rust CLI](./cli.md)\n* @ref:[Deploy to production](./deploy/index.md)\n* @ref:[Connectors](./connectors/index.md)\n\n## Discussion\n\nJoin the [Otoroshi](https://gitter.im/MAIF/otoroshi) channel on the [MAIF Gitter](https://gitter.im/MAIF)\n\n## Sources\n\nThe sources of Otoroshi are available on [Github](https://github.com/MAIF/otoroshi).\n\n## Logo\n\nYou can find the official Otoroshi logo [on GitHub](https://github.com/MAIF/otoroshi/blob/master/resources/otoroshi-logo.png). The Otoroshi logo has been created by François Galioto ([@fgalioto](https://twitter.com/fgalioto))\n\n## Changelog\n\nEvery release, along with the migration instructions, is documented on the [Github Releases](https://github.com/MAIF/otoroshi/releases) page.\n\n## Patrons\n\nThe work on Otoroshi was funded by MAIF with the help of the community.\n\n## Licence\n\nOtoroshi is Open Source and available under the [Apache 2 License](https://opensource.org/licenses/Apache-2.0)\n\n@@@ index\n\n* [About Otoroshi](about.md)\n* [Architecture](archi.md)\n* [Features](features.md)\n* [Quickstart](quickstart.md)\n* [Videos](videos.md)\n* [Get otoroshi](getotoroshi/index.md)\n* [First run](firstrun/index.md)\n* [Setup](setup/index.md)\n* [Using Otoroshi](usage/index.md)\n* [Integrations](integrations/index.md)\n* [Detailed topics](topics/index.md)\n* [Admin REST API](api.md)\n* [Embedding Otoroshi](./embedding.md)\n* [Official Rust CLI](cli.md)\n* [Deploy to production](deploy/index.md)\n* [Connectors](connectors/index.md)\n\n@@@\n"},{"name":"analytics.md","id":"/integrations/analytics.md","url":"/integrations/analytics.html","title":"Analytics","content":"# Analytics\n\nEach action and request on Otoroshi creates events that can be sent outside of Otoroshi for further usage. Those events can be sent using a webhook and/or through a Kafka topic.\n\n## Push events to Elasticsearch\n\nYou can use elastic search to store otoroshi events. To do this you have to configure the access to elasticsearch from `settings (cog icon) / Danger Zone` and expand the `Analytics: Elastic cluster (write)` section.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Read events from Elasticsearch\n\nYou can use elastic search to store otoroshi events. To do this you have to configure the access to elasticsearch from `settings (cog icon) / Danger Zone` and expand the `Analytics: Elastic dashboard datasource (read)` section.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Push events to WebHooks\n\nGo to `settings (cog icon) / Danger Zone` and expand the `Analytics: Webhooks` section.\n\n@@@ div { .centered-img }\n\n@@@\n\nHere you can configure the URL of the webhook and its headers if needed.\n\n## Push events to Kafka\n\nEvents can also be sent through a Kafka topic. Go to `settings (cog icon) / Danger Zone` and expand the `Analytics: Kafka` section.\n\n@@@ div { .centered-img }\n\n@@@\n\nFill the form, default values for topic names are :\n\n* `otoroshi-alerts`\n* `otoroshi-analytics`\n* `otoroshi-audits`\n\n@@@ warning\nIf you use trustore/keystore to access your kafka instances, the paths should be absolute and refers to host paths.\n@@@\n"},{"name":"auth0.md","id":"/integrations/auth0.md","url":"/integrations/auth0.html","title":"Auth0","content":"# Auth0\n\nYou can use Auth0 to log into Otoroshi's backoffice and Otoroshi's private apps.\n\nGo to `settings (cog icon) / Danger Zone` and expand the `Backoffice Auth0 settings` and `Private apps Auth0 settings` sections.\n\n@@@ div { .centered-img }\n\n@@@\n\nNow, you can fill the following fields :\n\n* `Client Id`\n* `Client Service`\n* `Domain`\n\nFor the `Callback URL` fields, use something like\n\n```\nhttps://otoroshi.oto.tools/backoffice/auth0/callback\nhttps://privateapps.oto.tools/privateapps/auth0/callback\n```\n\nOf course, you need to replace `otoroshi.oto.tools` and `privateapps.oto.tools` with your own domain and sub-domains. Don't forget to customize the callback URLs in your Auth0 backoffice too.\n\nNow if you logout, you will see the Auth0 option on the login screen\n\n@@@ div { .centered-img }\n\n@@@\n"},{"name":"clevercloud.md","id":"/integrations/clevercloud.md","url":"/integrations/clevercloud.html","title":"Clever Cloud","content":"# Clever Cloud\n\nOtoroshi provides an integration with Clever Cloud to create easily services based on application deployed on your Clever Cloud account.\nGo to `settings (cog icon) / Danger Zone` and expand the `CleverCloud settings` section.\n\n@@@ div { .centered-img }\n\n@@@\n\nFill the form with your CleverCloud credentials (https://www.clever-cloud.com/doc/clever-cloud-apis/cc-api/) and your CleverCloud `organization id`.\n\nOnce it's done, you will see a new menu in the side bar.\n\n@@@ div { .centered-img }\n\n@@@\n\nIf you click on it, you'll see a page listing all your apps deployed on Clever Cloud with buttons to create new services with the app as the target.\n\n@@@ div { .centered-img }\n\n@@@\n\nYou will also see a new button in the `Target` section of services to attach Clever Cloud applications as target for a service.\n\n@@@ div { .centered-img }\n\n@@@\n"},{"name":"index.md","id":"/integrations/index.md","url":"/integrations/index.html","title":"Third party Integrations","content":"# Third party Integrations\n\nOtoroshi provides some settings to interact with some third party systems.\n\n@@@ index\n\n* [Analytics](./analytics.md)\n* [Mailgun](./mailgun.md)\n* [StatsD / Datadog](./statsd.md)\n* [clevercloud](./clevercloud.md)\n\n@@@\n"},{"name":"mailgun.md","id":"/integrations/mailgun.md","url":"/integrations/mailgun.html","title":"Mailgun","content":"# Mailgun\n\nIf you want to receive Otoroshi alert by emails, you have to configure Otoroshi with your Mailgun credentials. Go to `settings (cog icon) / Danger Zone` and expand the `Mailgun settings` section.\n\n@@@ div { .centered-img }\n\n@@@\n\nFill the form with provided information on the `domain informations` page on Mailgun located at https://app.mailgun.com/app/domains/my.domain.\n\nThen, expand the `Alert settings` section and add email addresses separated by comma in the `Alert emails` field. **Don't forget to save.**\n\n@@@ div { .centered-img }\n\n@@@\n"},{"name":"statsd.md","id":"/integrations/statsd.md","url":"/integrations/statsd.html","title":"StatsD / Datadog","content":"# StatsD / Datadog\n\nOtoroshi provides a StatsD integration to monitor some technical metrics across all your Otoroshi instances.\nGo to `settings (cog icon) / Danger Zone` and expand the `Statsd settings` section.\n\n@@@ div { .centered-img }\n\n@@@\n\nAdd the host and port of the Statsd agent on your system.\nIf you're using Datadog, don't forget to check the `Datadog` switch.\n"},{"name":"quickstart.md","id":"/quickstart.md","url":"/quickstart.html","title":"Try Otoroshi in 5 minutes","content":"# Try Otoroshi in 5 minutes\n\nwhat you will need :\n\n* JDK 8\n* curl\n* 5 minutes of free time\n\nIf you don't/can't have these tools on your machine, you can start a sandboxed environment using here with the following command\n\n```sh\ndocker run -p \"8080:8080\" -it maif/otoroshi bash\n```\n\nor you can also try Otoroshi online with Google Cloud Shell if you're a user of the Google Cloud Platform\n\n@@@ div { .centered-img }\n[![Open in Cloud Shell](http://gstatic.com/cloudssh/images/open-btn.svg)](https://console.cloud.google.com/cloudshell/open?git_repo=https%3A%2F%2Fgithub.com%2Fmathieuancelin%2Fotoroshi-tutorial&page=shell&tutorial=tutorial.md)\n@@@\n\n## The elevator pitch\n\nOtoroshi is an awesome reverse proxy built with Scala that handles all the calls to and between your microservices without service locator and lets you change configuration dynamically at runtime.\n\n## I like sh but I really want to see the UI\n\nAs Otoroshi uses Otoroshi to serve its own admin UI and admin API, you won't be able to access the admin UI on `http://localhost:8080`. Otoroshi needs a domain name to know that you want to access the admin UI. By default, the admin UI is exposed on `http://otoroshi.oto.tools:8080`. Of course you can @ref:[configure](./firstrun/configfile.md#common-configuration) the domain of the subdomain. To configure access to the admin, just go to the [UI section of the quickstart](#what-about-the-ui).\n\n## Now some sh :)\n\n```sh\ncurl -L -o otoroshi.jar https://github.com/MAIF/otoroshi/releases/download/v1.4.18/otoroshi.jar\ncurl -L -o otoroshicli https://github.com/MAIF/otoroshi/releases/download/v1.4.18/linux-otoroshicli\n\nchmod +x otoroshicli\n\n# Run the Otoroshi server on Java 8\njava -jar otoroshi.jar &\n\n# Run the Otoroshi server on Java 9 and 10\njava --add-modules java.xml.bind -jar otoroshi.jar &\n\n# Check if admin api works\n./otoroshicli services all\n./otoroshicli apikeys all\n./otoroshicli groups all\n\n# Create a service that will proxy call to https://freegeoip.net through http://ip.geo.com:8080\n./otoroshicli services create --group default --name geo-ip-api --env prod \\\n --domain geo.com --subdomain ip --root /json/ --target https://freegeoip.net \\\n --public-pattern '/.*' --no-force-https\n\n# Then test it\n./otoroshicli tryout call \"http://127.0.0.1:8080/\" -X GET -H 'Host: ip.geo.com'\n# works with curl -X GET -H 'Host: ip.geo.com' \"http://127.0.0.1:8080/\" | jqn\n\n# Run 3 new microservices in 3 new terminal processes\n./otoroshicli tryout serve 9901\n./otoroshicli tryout serve 9902\n./otoroshicli tryout serve 9903\n\n# Create a service that will loadbalance between these 3 microservices and serves them through\n# http://api.hello.com:8080\n./otoroshicli services create --group default --id hello-api --name hello-api \\\n --env prod --domain hello.com --subdomain api --root / \\\n --target \"http://127.0.0.1:9901\" \\\n --target \"http://127.0.0.1:9902\" \\\n --public-pattern '/.*' --no-force-https --client-retries 3\n\n# Then test it multiple time to observe loadbalancing\n# You can also define '127.0.0.1 api.hello.com' in your /etc/hosts file and test it in your browser\n./otoroshicli tryout call \"http://127.0.0.1:8080/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\nsudo echo \"127.0.0.1 api.hello.com\" >> /etc/hosts\nopen \"http://api.hello.com:8080/\"\n\n# Then add a new target\n./otoroshicli services add-target hello-api --target=\"http://127.0.0.1:9903\"\n\n# Then test it multiple time to observe loadbalancing\n./otoroshicli tryout call \"http://127.0.0.1:8080/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\n\n# Then kill one of the microservices and test it multiple time to observe loadbalancing\n./otoroshicli tryout call \"http://127.0.0.1:8080/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\n\n# Then kill a second microservices and test it multiple time to observe loadbalancing\n./otoroshicli tryout call \"http://127.0.0.1:8080/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\n\n# Then kill the last microservices and test it to observe connection error\n./otoroshicli tryout call \"http://127.0.0.1:8080/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\n\n# Then delete your service\n./otoroshicli services delete hello-api\n```\n\n## What about the UI\n\nGo to http://otoroshi.oto.tools:8080/ and **log in with the credential generated in the logs** during first startup ;-)\n\nIf you want to know more about Otoroshi, you should continue reading the documentation starting with @ref:[how to get Otoroshi](./getotoroshi/index.md)\n\n## I don't have JDK 8 on my machine but I have Docker :)\n\nIf you want to use Docker, just follow these instructions\n\n```sh\n# here be careful, if you want to change the OTOROSHI_PORT, change the same value in the `otoroshicli.toml` config file\nexport OTOROSHI_PORT=8080\nexport LOCAL_IP_ADDRESS=999.999.999.999 # use your real local ip address here\n\ncurl -L -o otoroshicli https://github.com/MAIF/otoroshi/releases/download/v1.4.18/linux-otoroshicli\n\nchmod +x otoroshicli \n\ndocker run -p \"$OTOROSHI_PORT:8080\" maif/otoroshi &\n\n# Check if admin api works\n./otoroshicli services all\n./otoroshicli apikeys all\n./otoroshicli groups all\n\n# Create a service that will proxy call to https://freegeoip.net through http://ip.geo.com:$OTOROSHI_PORT\n./otoroshicli services create --group default --name geo-ip-api --env prod \\\n --domain geo.com --subdomain ip --root /json/ --target https://freegeoip.net \\\n --public-pattern '/.*' --no-force-https\n\n# Then test it\n./otoroshicli tryout call \"http://127.0.0.1:$OTOROSHI_PORT/\" -X GET -H 'Host: ip.geo.com'\n# works with curl -X GET -H 'Host: ip.geo.com' \"http://127.0.0.1:$OTOROSHI_PORT/\" | jqn\n\n# Run 3 new microservices in 3 new terminal processes\n./otoroshicli tryout serve 9901\n./otoroshicli tryout serve 9902\n./otoroshicli tryout serve 9903\n\n# Create a service that will loadbalance between these 3 microservices and serves them through\n# http://api.hello.com:$OTOROSHI_PORT\n./otoroshicli services create --group default --id hello-api --name hello-api \\\n --env prod --domain hello.com --subdomain api --root / \\\n --target \"http://$LOCAL_IP_ADDRESS:9901\" \\\n --target \"http://$LOCAL_IP_ADDRESS:9902\" \\\n --public-pattern '/.*' --no-force-https --client-retries 3\n\n# Then test it multiple time to observe loadbalancing\n# You can also define '127.0.0.1 api.hello.com' in your /etc/hosts file and test it in your browser\n./otoroshicli tryout call \"http://127.0.0.1:$OTOROSHI_PORT/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\nsudo echo \"127.0.0.1 api.hello.com\" >> /etc/hosts\nopen \"http://api.hello.com:$OTOROSHI_PORT/\"\n\n# Then add a new target\n./otoroshicli services add-target hello-api --target=\"http://$LOCAL_IP_ADDRESS:9903\"\n\n# Then test it multiple time to observe loadbalancing\n./otoroshicli tryout call \"http://127.0.0.1:$OTOROSHI_PORT/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\n\n# Then kill one of the microservices and test it multiple time to observe loadbalancing\n./otoroshicli tryout call \"http://127.0.0.1:$OTOROSHI_PORT/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\n\n# Then kill a second microservices and test it multiple time to observe loadbalancing\n./otoroshicli tryout call \"http://127.0.0.1:$OTOROSHI_PORT/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\n\n# Then kill the last microservices and test it to observe connection error\n./otoroshicli tryout call \"http://127.0.0.1:$OTOROSHI_PORT/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\n\n# Then delete your service\n./otoroshicli services delete hello-api\n```\n"},{"name":"admin.md","id":"/setup/admin.md","url":"/setup/admin.html","title":"Manage admin users","content":"# Manage admin users\n\n## Create admin user after the first run\n\nClick on the `Create an admin user` warning popup, or go to `settings (cog icon) / Admins`.\n\n@@@ div { .centered-img }\n\n@@@\n\nYou will see the list of registered admin users.\n\n@@@ div { .centered-img }\n\n@@@\n\nNow, enter informations about the new admin you want to create.\n\n@@@ div { .centered-img }\n\n@@@\n\nClick on `Register Admin`.\n\n@@@ div { .centered-img }\n\n@@@\n\nNow, you can discard the generated admin, confirm, then logout, login with the admin user you have just created and the danger popup will go away\n\n@@@ div { .centered-img }\n\n@@@\n\n## Create admin user with U2F device login\n\nGo to `settings (cog icon) / Admins`.\n\n@@@ div { .centered-img }\n\n@@@\n\nEnter informations about the new admin you want to create.\n\n@@@ div { .centered-img }\n\n@@@\n\nClick on `Register FIDO U2F Admin`.\n\nOtoroshi will ask you to plug your FIDO U2F device and touch it to complete registration.\n\n@@@ div { .centered-img }\n\n@@@\n\n@@@ warning\nTo be able to use FIDO U2F devices, Otoroshi must be served over https\n@@@\n\n## Discard admin user\n\nGo to `settings (cog icon) / Admins`, at the bottom of the page, you will see a list of admin users that you can discard. Just click on the `Discard User` button on the right side of the row and confirm that you actually want to discard an admin user.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Admin sessions management\n\nGo to `settings (cog icon) / Admins sessions`, you will see a list of active admin user sessions\n\n@@@ div { .centered-img }\n\n@@@\n\nYou can either discard single sessions one by one using the `Discard Session` on each targeted row of the list or discard all active sessions using the `Discard all sessions` button at the top of the page.\n"},{"name":"dangerzone.md","id":"/setup/dangerzone.md","url":"/setup/dangerzone.html","title":"Configure the Danger zone","content":"# Configure the Danger zone\n\nNow that you have an actual admin account, go to `setting (cog icon) / Danger Zone` in order to configure your Otoroshi instance.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Commons settings\n\nThis part allows you to configure various things :\n\n* `No Auth0 login` => allow you to disabled Auth0 login to the Otoroshi admin dashboard\n* `API read only` => disable `writes` on the Otoroshi admin api\n* `Use HTTP streaming` => use http streaming for each response. It should always be true\n* `Use circuit breakers` => allow usage of circuit breakers for each service\n* `Digitus medius` => change the character of endless HTTP responses from `0` to `🖕`\n* `Limit concurrent requests` => allow you to specify a max number of concurrent requests on an Otoroshi instance to avoid overloading\n* `Max concurrent requests` => max allowed number of concurrent requests on an Otoroshi instance to avoid overloading\n* `Max HTTP/1.0 response size` => max size of an HTTP/1.0 responses, because they are memory mapped\n* `Max local events` => number of events stored localy (alerts and audits)\n* `lines` => at least one (`prod`). for other, it will allow you to declare urls like `service.line.domain.tld`. For prod it will be `service.domain.tld`\n\n@@@ div { .centered-img }\n\n@@@\n\n## Whitelist / blacklist settings\n\nOtoroshi is capable of filtering request by ip address, allowing or blocking requests.\n\nOtoroshi also provides a fun feature called `Endless HTTP responses`. If you put an ip address in that field, then, for any http request on Otoroshi, every response will be 128 GB of `0`.\n\n@@@ div { .centered-img }\n\n@@@\n\n@@@ note\nNote that you may provide ip address with wildcard like the following `42.42.*.42` or `42.42.42.*` or `42.42.*.*`\n@@@\n\n## Global throttling settings\n\nOtoroshi is capable of managing throttling at a global level. Here you can configure number of authorized requests per second on a single Otoroshi instance and the number of authorized request per second for a unique ip address.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Analytics settings\n\nOne on the major features of Otoroshi is being able of generating internal events. Those events are not stored in Otoroshi's datastore but can be sent using `WebHooks`. You can configure those `WebHooks` from the `Danger Zone`.\n\nOtoroshi is also capable of reading some analytics and displays it from another MAIF product called `Omoïkane`. As Omoikane is not publicly available yet, is capable of storing events in an [Elastic](https://www.elastic.co/) cluster. For more information about analytics and what it does, just go to the @ref:[detailed chapter](../integrations/analytics.md)\n\n## Kafka settings\n\nOne on the major features of Otoroshi is being able of generating internal events. These events are not stored in Otoroshi's datastore but can be sent using a [Kafka message broker](https://kafka.apache.org/). You can configure Kafka access from the `Danger Zone`.\n\nBy default, Otoroshi's alert events will be sent on `otoroshi-alerts` topic, Otoroshi's audit events will be sent on `otoroshi-audits` topic and Otoroshi's traffic events will be sent on `otoroshi-analytics` topic.\n\n@@@ warning\nKeystore and truststore paths are optional local path on the server hosting Otoroshi\n@@@\n\n@@@ div { .centered-img }\n\n@@@\n\nFor more information about Kafka integration and what it does, just go to the @ref:[detailed chapter](../integrations/analytics.md)\n\n## Alerts settings\n\nEach time a dangerous action or something unusual is performed on Otoroshi, it will create an alert and store it. You can be notified for each of these alerts using `WebHooks` or emails. To do so, just add the `WebHook` URL and optional headers in the `Danger Zone` or any email address you want (you can add more than one email address).\n\n@@@ div { .centered-img }\n\n@@@\n\n## StatsD settings\n\nOtoroshi is capable of sending internal metrics to a StatsD agent. Just put the host and port of you StatsD agent in the `Danger Zone` to collect these metrics. If you using [Datadog](https://www.datadoghq.com), don't forget to check the dedicated button :)\n\n@@@ div { .centered-img }\n\n@@@\n\nFor more information about StatsD integration and what it does, just go to the @ref:[detailed chapter](../integrations/statsd.md)\n\n## Auth0 settings\n\nIt is possible to configure Otoroshi to allow admin users to log in through Auth0. Otoroshi also provides a feature called `Private apps.` that allows you to force login to an Auth0 domain before accessing an app. You can create an Auth0 client (https://manage.auth0.com/#/clients) for each of those features (admin users login and private apps login) and customize it with any rule you want (don't forget allowed callbacks, like `http://otoroshi.oto.tools:8080/backoffice/auth0/callback` and `http://privateapps.oto.tools:8080/backoffice/auth0/callback`).\n\nOnce you're done, go to the settings of each client (https://manage.auth0.com/#/clients/xxxxxxxxxxxxxxxx/settings) to get the information needed for the `Danger Zone`.\n\n@@@ div { .centered-img }\n\n@@@\n\n@@@ div { .centered-img }\n\n@@@\n\nFor more information about Auth0 integration and what it does, just go to the @ref:[detailed chapter](../integrations/auth0.md)\n\n## Mailgun settings\n\nIf you want to send emails for every alert generated by Otoroshi, you need to configure your Mailgun credentials in the `Danger Zone`. These parameters are provided in you Mailgun domain dashboard (i.e. https://app.mailgun.com/app/domains/my.domain.oto.tools) in the information section.\n\n@@@ div { .centered-img }\n\n@@@\n\nFor more information about Mailgun integration and what it does, just go to the @ref:[detailed chapter](../integrations/mailgun.md)\n\n## CleverCloud settings\n\nAs we built our products to run on Clever-Cloud, Otoroshi has a close integration with Clever-Cloud. In this section of `Danger Zone` you can configure how to access Clever-Cloud API.\n\nTo generate the needed value, please refers to [Clever-Cloud documentation](https://www.clever-cloud.com/doc/clever-cloud-apis/cc-api/)\n\n@@@ div { .centered-img }\n\n@@@\n\nFor more information about Clever-Cloud integration and what it does, just go to the @ref:[detailed chapter](../integrations/clevercloud.md)\n\n## Import / exports and panic mode\n\nFor more details about imports and exports, please go to the @ref:[dedicated chapter](../usage/8-importsexports.md)\n\nAbout panic mode, it's an unusual feature that allows you to discard all current admin. sessions, allows only admin users with U2F devices to log back, and pass the API in read-only mode. Only a person who has access to Otoroshi's datastore will be able to turn it back on.\n\n@@@ div { .centered-img }\n\n@@@\n"},{"name":"index.md","id":"/setup/index.md","url":"/setup/index.html","title":"Setup Otoroshi","content":"# Setup Otoroshi\n\nNow that Otoroshi is running, you are ready to log into the Otoroshi admin dashboard and setup your instance. Just go to :\n\nhttp://otoroshi.oto.tools:8080\n\nand you will see the following page\n \n@@@ div { .centered-img }\n\n@@@\n\nnow click on the login button and you will see the login page\n\n@@@ div { .centered-img }\n\n@@@\n\n@@@ warning\nUse the credentials generated in Otoroshi **logs** during **first run**.\n@@@\n\n@@@ div { .centered-img #first-login-example }\n\n@@@\n\n(of course, you can change this url dependending on the configuration you provided to Otoroshi).\n\nOnce logged in, the first screen you'll see should look like :\n\n@@@ div { .centered-img #first-login }\n\n@@@\n\nAs you can see, Otoroshi is not really happy about you being logged with a generated admin account.\n\nBut we will fix that in the next chapter\n\n@@@ index\n\n* [create admins](./admin.md)\n* [configure danger zone](./dangerzone.md)\n\n@@@\n"},{"name":"toc.md","id":"/toc.md","url":"/toc.html","title":"Table of contents","content":"# Table of contents\n\n@@toc\n\n"},{"name":"clustering.md","id":"/topics/clustering.md","url":"/topics/clustering.html","title":"Otoroshi clustering","content":"# Otoroshi clustering\n\nOtoroshi can work as a cluster by default as you can spin many Otoroshi servers using the same datastore or datastore cluster. In that case any instance is capable of serving services, Otoroshi admin UI, Otoroshi admin API, etc.\n\nBut sometimes, this is not enough. So Otoroshi provides an additional clustering model named `Leader / Workers` where there is a leader cluster ([control plane](https://en.wikipedia.org/wiki/Control_plane)), composed of Otoroshi instances backed by a datastore like Redis, Cassandra or Mongo, that is in charge of all `writes` to the datastore through Otoroshi admin UI and API, and a worker cluster ([data plane](https://en.wikipedia.org/wiki/Forwarding_plane)) composed of horizontally scalable Otoroshi instances, backed by a super fast in memory datastore, with the sole purpose of routing traffic to your services based on data synced from the leader cluster. With this distributed Otoroshi version, you can reach your goals of high availability, scalability and security.\n\nOtoroshi clustering only uses http internally (right now) to make communications between leaders and workers instances so it is fully compatible with PaaS providers like [Clever-Cloud](https://www.clever-cloud.com/en/) that only provide one external port for http traffic.\n\n@@@ div { .centered-img }\n\n\n*Fig. 1: Simplified view*\n@@@\n\n@@@ div { .centered-img }\n\n\n*Fig. 2: Deployment view*\n@@@\n\n## Cluster configuration\n\n```hocon\notoroshi {\n cluster {\n mode = \"leader\" # can be \"off\", \"leader\", \"worker\"\n compression = 4 # compression of the data sent between leader cluster and worker cluster. From -1 (disabled) to 9\n leader {\n name = ${?CLUSTER_LEADER_NAME} # name of the instance, if none, it will be generated\n urls = [\"http://127.0.0.1:8080\"] # urls to contact the leader cluster\n host = \"otoroshi-api.oto.tools\" # host of the otoroshi api in the leader cluster\n clientId = \"apikey-id\" # otoroshi api client id\n clientSecret = \"secret\" # otoroshi api client secret\n cacheStateFor = 4000 # state is cached during (ms)\n }\n worker {\n name = ${?CLUSTER_WORKER_NAME} # name of the instance, if none, it will be generated\n retries = 3 # number of retries when calling leader cluster\n timeout = 2000 # timeout when calling leader cluster\n state {\n retries = ${otoroshi.cluster.worker.retries} # number of retries when calling leader cluster on state sync\n pollEvery = 10000 # interval of time (ms) between 2 state sync\n timeout = ${otoroshi.cluster.worker.timeout} # timeout when calling leader cluster on state sync\n }\n quotas {\n retries = ${otoroshi.cluster.worker.retries} # number of retries when calling leader cluster on quotas sync\n pushEvery = 2000 # interval of time (ms) between 2 quotas sync\n timeout = ${otoroshi.cluster.worker.timeout} # timeout when calling leader cluster on quotas sync\n }\n }\n }\n}\n```\n\nyou can also use many env. variables to configure Otoroshi cluster\n\n```hocon\notoroshi {\n cluster {\n mode = ${?CLUSTER_MODE}\n compression = ${?CLUSTER_COMPRESSION}\n leader {\n name = ${?CLUSTER_LEADER_NAME}\n host = ${?CLUSTER_LEADER_HOST}\n url = ${?CLUSTER_LEADER_URL}\n clientId = ${?CLUSTER_LEADER_CLIENT_ID}\n clientSecret = ${?CLUSTER_LEADER_CLIENT_SECRET}\n groupingBy = ${?CLUSTER_LEADER_GROUP_BY}\n cacheStateFor = ${?CLUSTER_LEADER_CACHE_STATE_FOR}\n stateDumpPath = ${?CLUSTER_LEADER_DUMP_PATH}\n }\n worker {\n name = ${?CLUSTER_WORKER_NAME}\n retries = ${?CLUSTER_WORKER_RETRIES}\n timeout = ${?CLUSTER_WORKER_TIMEOUT}\n state {\n retries = ${?CLUSTER_WORKER_STATE_RETRIES}\n pollEvery = ${?CLUSTER_WORKER_POLL_EVERY}\n timeout = ${?CLUSTER_WORKER_POLL_TIMEOUT}\n }\n quotas {\n retries = ${?CLUSTER_WORKER_QUOTAS_RETRIES}\n pushEvery = ${?CLUSTER_WORKER_PUSH_EVERY}\n timeout = ${?CLUSTER_WORKER_PUSH_TIMEOUT}\n }\n }\n }\n}\n```\n\n@@@ warning\nYou **should** use HTTPS exposition for the Otoroshi API that will be used for data sync as sensitive informations are exchanged between control plane and data plane.\n@@@\n\n@@@ warning\nYou **must** have the same cluster configuration on every Otoroshi instance (worker/leader) with only names and mode changed for each instance. Some things in leader/worker are computed using configuration of their counterpart worker/leader.\n@@@\n\n## Cluster UI\n\nOnce an Otoroshi instance is launcher as cluster Leader, a new row of live metrics tile will be available on the home page of Otoroshi admin UI.\n\n@@@ div { .centered-img }\n\n@@@\n\nyou can also access a more detailed view of the cluster at `Settings (cog icon) / Cluster View`\n\n@@@ div { .centered-img }\n\n@@@\n\n## Run examples\n\nfor leader \n\n```sh\njava -Dhttp.port=8091 -Dhttps.port=9091 -Dotoroshi.cluster.mode=leader -jar otoroshi.jar\n```\n\nfor worker\n\n```sh\njava -Dhttp.port=8092 -Dhttps.port=9092 -Dotoroshi.cluster.mode=worker \\\n -Dotoroshi.cluster.leader.urls.0=http://127.0.0.1:8091 -jar otoroshi.jar\n```\n"},{"name":"index.md","id":"/topics/index.md","url":"/topics/index.html","title":"Detailed topics","content":"# Detailed topics\n\nIn this sections, you will find informations about various topics supported by Otoroshi\n\n@@@ index\n\n* [Chaos engineering with the Snow Monkey](./snow-monkey.md)\n* [Service mesh](./service-mesh.md)\n* [JWT Tokens verification](./jwt-verifications.md)\n* [SSL/TLS termination with Otoroshi](./ssl.md)\n* [Mutual TLS with Otoroshi](./mtls.md)\n* [Otoroshi Clustering](./clustering.md)\n* [Requests transformation](./req-transformers.md)\n* [Otoroshi monitoring](./monitoring.md)\n\n@@@\n"},{"name":"jwt-verifications.md","id":"/topics/jwt-verifications.md","url":"/topics/jwt-verifications.html","title":"JWT Tokens verification","content":"# JWT Tokens verification\n\nSometimes, it can be pretty useful to verify Jwt tokens coming from other provider on some services. Otoroshi provides a tool to do that per service. In the Service descriptor page, you can find a `Jwt token Verification` section dedicated to this topic.\n\n## Service descriptor local verifications\n\n@@@ div { .centered-img }\n\n@@@\n\nin this section you can select the type of verification you can choose if the verifier is local to the `Service descriptor` or reference a global one.\n\nYou can also enabled/disable jwt verification and activate strict mode. In strict mode, requests will be rejected if the jwt token is not found.\n\n### Jwt token location\n\nYou can use the `Source` selector to specify where the Jwt token can be found. \n\n* in a query string param\n\n@@@ div { .centered-img }\n\n@@@\n\n* in a header\n\n@@@ div { .centered-img }\n\n@@@\n\n* in a cookie\n\n@@@ div { .centered-img }\n\n@@@\n\n### Jwt signing\n\nYou can use the `Algo.` selector to specify the signing algorithm to use to verifiy the token\n\n@@@ div { .centered-img }\n\n@@@\n\nyou can choose between\n\n* Hmac + SHA256\n* Hmac + SHA384\n* Hmac + SHA512\n* RSA + SHA256\n* RSA + SHA384\n* RSA + SHA512\n* Elliptic Curve + SHA256\n* Elliptic Curve + SHA384\n* Elliptic Curve + SHA512\n\n@@@ div { .centered-img }\n\n@@@\n\nYou can use syntax like `${env.MY_ENV_VAR}` or `${config.my.config.path}` to provide secret/keys values. \n\n\n### Just verify signature and fields value\n\nUsing the `Verif. strategy` selector, you can choose `Verify jwt token`. This will verify if the token is signed using the settings from `jwt signing` section and the value of the fields provided in `Verify token fields`. Then the token will be send to the target just like that.\n\n@@@ div { .centered-img }\n\n@@@\n\n### Re-sign the token\n\nUsing the `Verif. strategy` selector, you can choose `Verify and re-sign jwt token`. This will verify if the token is signed using the settings from `jwt signing` section and the value of the fields provided in `Verify token fields`. Then the token will be re-signed using the settings provided in `Re-sign algo` and will be send to the target.\n\n@@@ div { .centered-img }\n\n@@@\n\n### Transform the token\n\nUsing the `Verif. strategy` selector, you can choose `Verify, re-sign and transform jwt token`. This will verify if the token is signed using the settings from `jwt signing` section and the value of the fields provided in `Verify token fields`. Then the token will be re-signed using the settings provided in `Re-sign algo`. You can also change the location of the token using `Token location`, remove fields using `Remove token fields`, set fields value using `Set token fields` and even rename fields using `Rename token fields`.\n\n@@@ div { .centered-img }\n\n@@@\n\nYou can also use a mini expression language in `Set token fields`. You just have to add expressions in values like `${expression}`. Supported expressions are the following :\n\n* `${date}` => set the current date\n* `${date.format('dd/MM/yyyy')}` => set the current date formatted with the format you want\n* `${token.fieldName}` => get the value of the field named `fieldName`\n* `${token.fieldName.replace('a', 'b')}` => get the value of the field named `fieldName` and replace `a` with `b`\n* `${token.fieldName.replaceAll('[0-9]', '-')}` => get the value of the field named `fieldName` and replace digits with `-`\n\nyou can of course use multiple expressions in one field like `my-value-is-${date}-with${token.user}`\n\n## Global verifications\n\nYou can create global jwt verifiers and reference them in your services (from the `Type` selector). When you set the type of verification to `Reference to a global definition`, you can choose an existing global jwt verifier\n\n@@@ div { .centered-img }\n\n@@@\n\nTo create a global verifier, go to `Settings (cog icon) / Global Jwt Verifiers` and it will display the list of global verifiers.\n\n@@@ div { .centered-img }\n\n@@@\n\nyou can them create, edit or delete verifiers\n\n@@@ div { .centered-img }\n\n@@@\n\n"},{"name":"monitoring.md","id":"/topics/monitoring.md","url":"/topics/monitoring.html","title":"Monitoring Otoroshi","content":"# Monitoring Otoroshi\n\nThe Otoroshi API exposes two endpoints for \n\n* `/health`: the health of the Otoroshi instance\n* `/metrics`: the metrics of the Otoroshi instance, either in JSON or Prometheus format using the `Accept` header (with `application/json` / `application/prometheus` values) or the `format` query param (with `json` or `prometheus` values)\n\n## Endpoints security\n\nThe two endpoints are exposed publicly on the Otoroshi admin api. But you can remove the corresponding public pattern and query the endpoints using standard apikeys. If you don't want to use apikeys but don't want to expose the endpoints publicly, you can defined two config. variables (`app.health.accessKey` or `HEALTH_ACCESS_KEY` and `otoroshi.metrics.accessKey` or `OTOROSHI_METRICS_ACCESS_KEY`) that will hold an access key for the endpoints. Then you can call the endpoints with an `access_key` query param with the value defined in the config. If you don't defined `otoroshi.metrics.accessKey` but define `app.health.accessKey`, `otoroshi.metrics.accessKey` will have the value of `app.health.accessKey`.\n \n## Examples\n\nlet say `app.health.accessKey` has value `MILpkVv6f2kG9Xmnc4mFIYRU4rTxHVGkxvB0hkQLZwEaZgE2hgbOXiRsN1DBnbtY`\n\n```sh\n$ curl http://otoroshi-api.oto.tools:8080/health\\?access_key\\=MILpkVv6f2kG9Xmnc4mFIYRU4rTxHVGkxvB0hkQLZwEaZgE2hgbOXiRsN1DBnbtY\n{\"otoroshi\":\"healthy\",\"datastore\":\"healthy\"}\n\n$ curl -H 'Accept: application/json' http://otoroshi-api.oto.tools:8080/metrics\\?access_key\\=MILpkVv6f2kG9Xmnc4mFIYRU4rTxHVGkxvB0hkQLZwEaZgE2hgbOXiRsN1DBnbtY\n{\"version\":\"4.0.0\",\"gauges\":{\"attr.app.commit\":{\"value\":\"xxxx\"},\"attr.app.id\":{\"value\":\"xxxx\"},\"attr.cluster.mode\":{\"value\":\"Leader\"},\"attr.cluster.name\":{\"value\":\"otoroshi-leader-0\"},\"attr.instance.env\":{\"value\":\"prod\"},\"attr.instance.id\":{\"value\":\"xxxx\"},\"attr.instance.number\":{\"value\":\"0\"},\"attr.jvm.cpu.usage\":{\"value\":136},\"attr.jvm.heap.size\":{\"value\":1409},\"attr.jvm.heap.used\":{\"value\":112},\"internals.0.concurrent-requests\":{\"value\":1},\"internals.global.throttling-quotas\":{\"value\":2},\"jvm.attr.name\":{\"value\":\"2085@xxxx\"},\"jvm.attr.uptime\":{\"value\":2296900},\"jvm.attr.vendor\":{\"value\":\"JDK11\"},\"jvm.gc.PS-MarkSweep.count\":{\"value\":3},\"jvm.gc.PS-MarkSweep.time\":{\"value\":261},\"jvm.gc.PS-Scavenge.count\":{\"value\":12},\"jvm.gc.PS-Scavenge.time\":{\"value\":161},\"jvm.memory.heap.committed\":{\"value\":1477967872},\"jvm.memory.heap.init\":{\"value\":1690304512},\"jvm.memory.heap.max\":{\"value\":3005218816},\"jvm.memory.heap.usage\":{\"value\":0.03916456777568639},\"jvm.memory.heap.used\":{\"value\":117698096},\"jvm.memory.non-heap.committed\":{\"value\":166445056},\"jvm.memory.non-heap.init\":{\"value\":7667712},\"jvm.memory.non-heap.max\":{\"value\":994050048},\"jvm.memory.non-heap.usage\":{\"value\":0.1523920694986979},\"jvm.memory.non-heap.used\":{\"value\":151485344},\"jvm.memory.pools.CodeHeap-'non-nmethods'.committed\":{\"value\":2555904},\"jvm.memory.pools.CodeHeap-'non-nmethods'.init\":{\"value\":2555904},\"jvm.memory.pools.CodeHeap-'non-nmethods'.max\":{\"value\":5832704},\"jvm.memory.pools.CodeHeap-'non-nmethods'.usage\":{\"value\":0.28408093398876405},\"jvm.memory.pools.CodeHeap-'non-nmethods'.used\":{\"value\":1656960},\"jvm.memory.pools.CodeHeap-'non-profiled-nmethods'.committed\":{\"value\":11796480},\"jvm.memory.pools.CodeHeap-'non-profiled-nmethods'.init\":{\"value\":2555904},\"jvm.memory.pools.CodeHeap-'non-profiled-nmethods'.max\":{\"value\":122912768},\"jvm.memory.pools.CodeHeap-'non-profiled-nmethods'.usage\":{\"value\":0.09536102872567315},\"jvm.memory.pools.CodeHeap-'non-profiled-nmethods'.used\":{\"value\":11721088},\"jvm.memory.pools.CodeHeap-'profiled-nmethods'.committed\":{\"value\":37355520},\"jvm.memory.pools.CodeHeap-'profiled-nmethods'.init\":{\"value\":2555904},\"jvm.memory.pools.CodeHeap-'profiled-nmethods'.max\":{\"value\":122912768},\"jvm.memory.pools.CodeHeap-'profiled-nmethods'.usage\":{\"value\":0.2538573047187417},\"jvm.memory.pools.CodeHeap-'profiled-nmethods'.used\":{\"value\":31202304},\"jvm.memory.pools.Compressed-Class-Space.committed\":{\"value\":14942208},\"jvm.memory.pools.Compressed-Class-Space.init\":{\"value\":0},\"jvm.memory.pools.Compressed-Class-Space.max\":{\"value\":367001600},\"jvm.memory.pools.Compressed-Class-Space.usage\":{\"value\":0.033858838762555805},\"jvm.memory.pools.Compressed-Class-Space.used\":{\"value\":12426248},\"jvm.memory.pools.Metaspace.committed\":{\"value\":99794944},\"jvm.memory.pools.Metaspace.init\":{\"value\":0},\"jvm.memory.pools.Metaspace.max\":{\"value\":375390208},\"jvm.memory.pools.Metaspace.usage\":{\"value\":0.25168142904782426},\"jvm.memory.pools.Metaspace.used\":{\"value\":94478744},\"jvm.memory.pools.PS-Eden-Space.committed\":{\"value\":349700096},\"jvm.memory.pools.PS-Eden-Space.init\":{\"value\":422576128},\"jvm.memory.pools.PS-Eden-Space.max\":{\"value\":1110966272},\"jvm.memory.pools.PS-Eden-Space.usage\":{\"value\":0.07505125052077188},\"jvm.memory.pools.PS-Eden-Space.used\":{\"value\":83379408},\"jvm.memory.pools.PS-Eden-Space.used-after-gc\":{\"value\":0},\"jvm.memory.pools.PS-Old-Gen.committed\":{\"value\":1127219200},\"jvm.memory.pools.PS-Old-Gen.init\":{\"value\":1127219200},\"jvm.memory.pools.PS-Old-Gen.max\":{\"value\":2253914112},\"jvm.memory.pools.PS-Old-Gen.usage\":{\"value\":0.014950035505168354},\"jvm.memory.pools.PS-Old-Gen.used\":{\"value\":33696096},\"jvm.memory.pools.PS-Old-Gen.used-after-gc\":{\"value\":23791152},\"jvm.memory.pools.PS-Survivor-Space.committed\":{\"value\":1048576},\"jvm.memory.pools.PS-Survivor-Space.init\":{\"value\":70254592},\"jvm.memory.pools.PS-Survivor-Space.max\":{\"value\":1048576},\"jvm.memory.pools.PS-Survivor-Space.usage\":{\"value\":0.59375},\"jvm.memory.pools.PS-Survivor-Space.used\":{\"value\":622592},\"jvm.memory.pools.PS-Survivor-Space.used-after-gc\":{\"value\":622592},\"jvm.memory.total.committed\":{\"value\":1644412928},\"jvm.memory.total.init\":{\"value\":1697972224},\"jvm.memory.total.max\":{\"value\":3999268864},\"jvm.memory.total.used\":{\"value\":269184904},\"jvm.thread.blocked.count\":{\"value\":0},\"jvm.thread.count\":{\"value\":82},\"jvm.thread.daemon.count\":{\"value\":11},\"jvm.thread.deadlock.count\":{\"value\":0},\"jvm.thread.deadlocks\":{\"value\":[]},\"jvm.thread.new.count\":{\"value\":0},\"jvm.thread.runnable.count\":{\"value\":25},\"jvm.thread.terminated.count\":{\"value\":0},\"jvm.thread.timed_waiting.count\":{\"value\":10},\"jvm.thread.waiting.count\":{\"value\":47}},\"counters\":{},\"histograms\":{},\"meters\":{},\"timers\":{}}\n\n$ curl -H 'Accept: application/prometheus' http://otoroshi-api.oto.tools:8080/metrics\\?access_key\\=MILpkVv6f2kG9Xmnc4mFIYRU4rTxHVGkxvB0hkQLZwEaZgE2hgbOXiRsN1DBnbtY\n# TYPE attr_jvm_cpu_usage gauge\nattr_jvm_cpu_usage 83.0\n# TYPE attr_jvm_heap_size gauge\nattr_jvm_heap_size 1409.0\n# TYPE attr_jvm_heap_used gauge\nattr_jvm_heap_used 220.0\n# TYPE internals_0_concurrent_requests gauge\ninternals_0_concurrent_requests 1.0\n# TYPE internals_global_throttling_quotas gauge\ninternals_global_throttling_quotas 3.0\n# TYPE jvm_attr_uptime gauge\njvm_attr_uptime 2372614.0\n# TYPE jvm_gc_PS_MarkSweep_count gauge\njvm_gc_PS_MarkSweep_count 3.0\n# TYPE jvm_gc_PS_MarkSweep_time gauge\njvm_gc_PS_MarkSweep_time 261.0\n# TYPE jvm_gc_PS_Scavenge_count gauge\njvm_gc_PS_Scavenge_count 12.0\n# TYPE jvm_gc_PS_Scavenge_time gauge\njvm_gc_PS_Scavenge_time 161.0\n# TYPE jvm_memory_heap_committed gauge\njvm_memory_heap_committed 1.477967872E9\n# TYPE jvm_memory_heap_init gauge\njvm_memory_heap_init 1.690304512E9\n# TYPE jvm_memory_heap_max gauge\njvm_memory_heap_max 3.005218816E9\n# TYPE jvm_memory_heap_usage gauge\njvm_memory_heap_usage 0.07680553268571043\n# TYPE jvm_memory_heap_used gauge\njvm_memory_heap_used 2.30817432E8\n# TYPE jvm_memory_non_heap_committed gauge\njvm_memory_non_heap_committed 1.66510592E8\n# TYPE jvm_memory_non_heap_init gauge\njvm_memory_non_heap_init 7667712.0\n# TYPE jvm_memory_non_heap_max gauge\njvm_memory_non_heap_max 9.94050048E8\n# TYPE jvm_memory_non_heap_usage gauge\njvm_memory_non_heap_usage 0.15262878997416435\n# TYPE jvm_memory_non_heap_used gauge\njvm_memory_non_heap_used 1.51720656E8\n# TYPE jvm_memory_pools_CodeHeap__non_nmethods__committed gauge\njvm_memory_pools_CodeHeap__non_nmethods__committed 2555904.0\n# TYPE jvm_memory_pools_CodeHeap__non_nmethods__init gauge\njvm_memory_pools_CodeHeap__non_nmethods__init 2555904.0\n# TYPE jvm_memory_pools_CodeHeap__non_nmethods__max gauge\njvm_memory_pools_CodeHeap__non_nmethods__max 5832704.0\n# TYPE jvm_memory_pools_CodeHeap__non_nmethods__usage gauge\njvm_memory_pools_CodeHeap__non_nmethods__usage 0.28408093398876405\n# TYPE jvm_memory_pools_CodeHeap__non_nmethods__used gauge\njvm_memory_pools_CodeHeap__non_nmethods__used 1656960.0\n# TYPE jvm_memory_pools_CodeHeap__non_profiled_nmethods__committed gauge\njvm_memory_pools_CodeHeap__non_profiled_nmethods__committed 1.1862016E7\n# TYPE jvm_memory_pools_CodeHeap__non_profiled_nmethods__init gauge\njvm_memory_pools_CodeHeap__non_profiled_nmethods__init 2555904.0\n# TYPE jvm_memory_pools_CodeHeap__non_profiled_nmethods__max gauge\njvm_memory_pools_CodeHeap__non_profiled_nmethods__max 1.22912768E8\n# TYPE jvm_memory_pools_CodeHeap__non_profiled_nmethods__usage gauge\njvm_memory_pools_CodeHeap__non_profiled_nmethods__usage 0.09610562183417755\n# TYPE jvm_memory_pools_CodeHeap__non_profiled_nmethods__used gauge\njvm_memory_pools_CodeHeap__non_profiled_nmethods__used 1.1812608E7\n# TYPE jvm_memory_pools_CodeHeap__profiled_nmethods__committed gauge\njvm_memory_pools_CodeHeap__profiled_nmethods__committed 3.735552E7\n# TYPE jvm_memory_pools_CodeHeap__profiled_nmethods__init gauge\njvm_memory_pools_CodeHeap__profiled_nmethods__init 2555904.0\n# TYPE jvm_memory_pools_CodeHeap__profiled_nmethods__max gauge\njvm_memory_pools_CodeHeap__profiled_nmethods__max 1.22912768E8\n# TYPE jvm_memory_pools_CodeHeap__profiled_nmethods__usage gauge\njvm_memory_pools_CodeHeap__profiled_nmethods__usage 0.25493618368435084\n# TYPE jvm_memory_pools_CodeHeap__profiled_nmethods__used gauge\njvm_memory_pools_CodeHeap__profiled_nmethods__used 3.1334912E7\n# TYPE jvm_memory_pools_Compressed_Class_Space_committed gauge\njvm_memory_pools_Compressed_Class_Space_committed 1.4942208E7\n# TYPE jvm_memory_pools_Compressed_Class_Space_init gauge\njvm_memory_pools_Compressed_Class_Space_init 0.0\n# TYPE jvm_memory_pools_Compressed_Class_Space_max gauge\njvm_memory_pools_Compressed_Class_Space_max 3.670016E8\n# TYPE jvm_memory_pools_Compressed_Class_Space_usage gauge\njvm_memory_pools_Compressed_Class_Space_usage 0.03386023385184152\n# TYPE jvm_memory_pools_Compressed_Class_Space_used gauge\njvm_memory_pools_Compressed_Class_Space_used 1.242676E7\n# TYPE jvm_memory_pools_Metaspace_committed gauge\njvm_memory_pools_Metaspace_committed 9.9794944E7\n# TYPE jvm_memory_pools_Metaspace_init gauge\njvm_memory_pools_Metaspace_init 0.0\n# TYPE jvm_memory_pools_Metaspace_max gauge\njvm_memory_pools_Metaspace_max 3.75390208E8\n# TYPE jvm_memory_pools_Metaspace_usage gauge\njvm_memory_pools_Metaspace_usage 0.25170985813247426\n# TYPE jvm_memory_pools_Metaspace_used gauge\njvm_memory_pools_Metaspace_used 9.4489416E7\n# TYPE jvm_memory_pools_PS_Eden_Space_committed gauge\njvm_memory_pools_PS_Eden_Space_committed 3.49700096E8\n# TYPE jvm_memory_pools_PS_Eden_Space_init gauge\njvm_memory_pools_PS_Eden_Space_init 4.22576128E8\n# TYPE jvm_memory_pools_PS_Eden_Space_max gauge\njvm_memory_pools_PS_Eden_Space_max 1.110966272E9\n# TYPE jvm_memory_pools_PS_Eden_Space_usage gauge\njvm_memory_pools_PS_Eden_Space_usage 0.17698545577448457\n# TYPE jvm_memory_pools_PS_Eden_Space_used gauge\njvm_memory_pools_PS_Eden_Space_used 1.96624872E8\n# TYPE jvm_memory_pools_PS_Eden_Space_used_after_gc gauge\njvm_memory_pools_PS_Eden_Space_used_after_gc 0.0\n# TYPE jvm_memory_pools_PS_Old_Gen_committed gauge\njvm_memory_pools_PS_Old_Gen_committed 1.1272192E9\n# TYPE jvm_memory_pools_PS_Old_Gen_init gauge\njvm_memory_pools_PS_Old_Gen_init 1.1272192E9\n# TYPE jvm_memory_pools_PS_Old_Gen_max gauge\njvm_memory_pools_PS_Old_Gen_max 2.253914112E9\n# TYPE jvm_memory_pools_PS_Old_Gen_usage gauge\njvm_memory_pools_PS_Old_Gen_usage 0.014950035505168354\n# TYPE jvm_memory_pools_PS_Old_Gen_used gauge\njvm_memory_pools_PS_Old_Gen_used 3.3696096E7\n# TYPE jvm_memory_pools_PS_Old_Gen_used_after_gc gauge\njvm_memory_pools_PS_Old_Gen_used_after_gc 2.3791152E7\n# TYPE jvm_memory_pools_PS_Survivor_Space_committed gauge\njvm_memory_pools_PS_Survivor_Space_committed 1048576.0\n# TYPE jvm_memory_pools_PS_Survivor_Space_init gauge\njvm_memory_pools_PS_Survivor_Space_init 7.0254592E7\n# TYPE jvm_memory_pools_PS_Survivor_Space_max gauge\njvm_memory_pools_PS_Survivor_Space_max 1048576.0\n# TYPE jvm_memory_pools_PS_Survivor_Space_usage gauge\njvm_memory_pools_PS_Survivor_Space_usage 0.59375\n# TYPE jvm_memory_pools_PS_Survivor_Space_used gauge\njvm_memory_pools_PS_Survivor_Space_used 622592.0\n# TYPE jvm_memory_pools_PS_Survivor_Space_used_after_gc gauge\njvm_memory_pools_PS_Survivor_Space_used_after_gc 622592.0\n# TYPE jvm_memory_total_committed gauge\njvm_memory_total_committed 1.644478464E9\n# TYPE jvm_memory_total_init gauge\njvm_memory_total_init 1.697972224E9\n# TYPE jvm_memory_total_max gauge\njvm_memory_total_max 3.999268864E9\n# TYPE jvm_memory_total_used gauge\njvm_memory_total_used 3.82665128E8\n# TYPE jvm_thread_blocked_count gauge\njvm_thread_blocked_count 0.0\n# TYPE jvm_thread_count gauge\njvm_thread_count 82.0\n# TYPE jvm_thread_daemon_count gauge\njvm_thread_daemon_count 11.0\n# TYPE jvm_thread_deadlock_count gauge\njvm_thread_deadlock_count 0.0\n# TYPE jvm_thread_new_count gauge\njvm_thread_new_count 0.0\n# TYPE jvm_thread_runnable_count gauge\njvm_thread_runnable_count 25.0\n# TYPE jvm_thread_terminated_count gauge\njvm_thread_terminated_count 0.0\n# TYPE jvm_thread_timed_waiting_count gauge\njvm_thread_timed_waiting_count 10.0\n# TYPE jvm_thread_waiting_count gauge\njvm_thread_waiting_count 47.0\n```"},{"name":"mtls.md","id":"/topics/mtls.md","url":"/topics/mtls.html","title":"Mutual TLS with Otoroshi","content":"# Mutual TLS with Otoroshi\n\nOtoroshi support mutual TLS out of the box. mTLS from client to Otoroshi and from Otoroshi to targets are supported. In this article we will see how to configure Otoroshi to use end-to-end mTLS. All code and files used in this articles can be found on the [Otoroshi github](https://github.com/MAIF/otoroshi/tree/master/demos/mtls)\n\n@@@ note { title=\"Experimental Feature\" }\nDynamic Mutual TLS is an experimental feature. It can change until it becomess an official feature\n@@@\n\n## End-to-end mTLS\n\nThe use case is the following :\n\n@@@ div { .centered-img }\n\n@@@\n\nfor this demo you will have to edit your `/etc/hosts` file to add the following entries\n\n```\n127.0.0.1 api.backend.lol api.frontend.lol www.backend.lol www.frontend.lol validation.backend.lol\n```\n\n### Create certificates\n\nBut first we need to generate some certificates to make the demo work\n\n```sh\nmkdir mtls-demo\ncd mtls-demo\nmkdir ca\nmkdir server\nmkdir client\n\n# create a certificate authority key, use password as pass phrase\nopenssl genrsa -out ./ca/ca-backend.key 4096\n# remove pass phrase\nopenssl rsa -in ./ca/ca-backend.key -out ./ca/ca-backend.key\n# generate the certificate authority cert\nopenssl req -new -x509 -sha256 -days 730 -key ./ca/ca-backend.key -out ./ca/ca-backend.cer -subj \"/CN=MTLSB\"\n\n\n# create a certificate authority key, use password as pass phrase\nopenssl genrsa -out ./ca/ca-frontend.key 2048\n# remove pass phrase\nopenssl rsa -in ./ca/ca-frontend.key -out ./ca/ca-frontend.key\n# generate the certificate authority cert\nopenssl req -new -x509 -sha256 -days 730 -key ./ca/ca-frontend.key -out ./ca/ca-frontend.cer -subj \"/CN=MTLSF\"\n\n\n# now create the backend cert key, use password as pass phrase\nopenssl genrsa -out ./server/_.backend.lol.key 2048\n# remove pass phrase\nopenssl rsa -in ./server/_.backend.lol.key -out ./server/_.backend.lol.key\n# generate the csr for the certificate\nopenssl req -new -key ./server/_.backend.lol.key -sha256 -out ./server/_.backend.lol.csr -subj \"/CN=*.backend.lol\"\n# generate the certificate\nopenssl x509 -req -days 365 -sha256 -in ./server/_.backend.lol.csr -CA ./ca/ca-backend.cer -CAkey ./ca/ca-backend.key -set_serial 1 -out ./server/_.backend.lol.cer\n# verify the certificate, should output './server/_.backend.lol.cer: OK'\nopenssl verify -CAfile ./ca/ca-backend.cer ./server/_.backend.lol.cer\n\n\n# now create the frontend cert key, use password as pass phrase\nopenssl genrsa -out ./server/_.frontend.lol.key 2048\n# remove pass phrase\nopenssl rsa -in ./server/_.frontend.lol.key -out ./server/_.frontend.lol.key\n# generate the csr for the certificate\nopenssl req -new -key ./server/_.frontend.lol.key -sha256 -out ./server/_.frontend.lol.csr -subj \"/CN=*.frontend.lol\"\n# generate the certificate\nopenssl x509 -req -days 365 -sha256 -in ./server/_.frontend.lol.csr -CA ./ca/ca-frontend.cer -CAkey ./ca/ca-frontend.key -set_serial 1 -out ./server/_.frontend.lol.cer\n# verify the certificate, should output './server/_.frontend.lol.cer: OK'\nopenssl verify -CAfile ./ca/ca-frontend.cer ./server/_.frontend.lol.cer\n\n\n# now create the client cert key for backend, use password as pass phrase\nopenssl genrsa -out ./client/_.backend.lol.key 2048\n# remove pass phrase\nopenssl rsa -in ./client/_.backend.lol.key -out ./client/_.backend.lol.key\n# generate the csr for the certificate\nopenssl req -new -key ./client/_.backend.lol.key -out ./client/_.backend.lol.csr -subj \"/CN=*.backend.lol\"\n# generate the certificate\nopenssl x509 -req -days 365 -sha256 -in ./client/_.backend.lol.csr -CA ./ca/ca-backend.cer -CAkey ./ca/ca-backend.key -set_serial 2 -out ./client/_.backend.lol.cer\n# generate a pkcs12 version of the cert and key, use password as password\nopenssl pkcs12 -export -clcerts -in client/_.backend.lol.cer -inkey client/_.backend.lol.key -out client/_.backend.lol.p12\n\n\n# now create the client cert key for frontend, use password as pass phrase\nopenssl genrsa -out ./client/_.frontend.lol.key 2048\n# remove pass phrase\nopenssl rsa -in ./client/_.frontend.lol.key -out ./client/_.frontend.lol.key\n# generate the csr for the certificate\nopenssl req -new -key ./client/_.frontend.lol.key -out ./client/_.frontend.lol.csr -subj \"/CN=*.frontend.lol\"\n# generate the certificate\nopenssl x509 -req -days 365 -sha256 -in ./client/_.frontend.lol.csr -CA ./ca/ca-frontend.cer -CAkey ./ca/ca-frontend.key -set_serial 2 -out ./client/_.frontend.lol.cer\n# generate a pkcs12 version of the cert and key, use password as password\nopenssl pkcs12 -export -clcerts -in client/_.frontend.lol.cer -inkey client/_.frontend.lol.key -out client/_.frontend.lol.p12\n```\n\nonce it's done, you should have something like\n\n```sh\n$ tree\n.\n├── backend.js\n├── ca\n│   ├── ca-backend.cer\n│   ├── ca-backend.key\n│   ├── ca-frontend.cer\n│   └── ca-frontend.key\n├── client\n│   ├── _.backend.lol.cer\n│   ├── _.backend.lol.csr\n│   ├── _.backend.lol.key\n│   ├── _.backend.lol.p12\n│   ├── _.frontend.lol.cer\n│   ├── _.frontend.lol.csr\n│   ├── _.frontend.lol.key\n│   └── _.frontend.lol.p12\n└── server\n ├── _.backend.lol.cer\n ├── _.backend.lol.csr\n ├── _.backend.lol.key\n ├── _.frontend.lol.cer\n ├── _.frontend.lol.csr\n └── _.frontend.lol.key\n\n3 directories, 18 files\n```\n\n### The backend service \n\nnow, let's create a backend service using nodejs. Create a file named `backend.js`\n\n```sh\ntouch backend.js\n```\n\nand put the following content\n\n```js\nconst fs = require('fs'); \nconst https = require('https'); \n\nconst options = { \n key: fs.readFileSync('./server/_.backend.lol.key'), \n cert: fs.readFileSync('./server/_.backend.lol.cer'), \n ca: fs.readFileSync('./ca/ca-backend.cer'), \n}; \n\nhttps.createServer(options, (req, res) => { \n res.writeHead(200, {\n 'Content-Type': 'application/json'\n }); \n res.end(JSON.stringify({ message: 'Hello World!' }) + \"\\n\"); \n}).listen(8444);\n```\n\nto run the server, just do \n\n```sh\nnode ./backend.js\n```\n\nnow you can try your server with\n\n```sh\ncurl --cacert ./ca/ca-backend.cer https://api.backend.lol:8444/\n# will print {\"message\":\"Hello World!\"}\n```\n\nnow modify your backend server to ensure that the client provides a client certificate like:\n\n```js\nconst fs = require('fs'); \nconst https = require('https'); \n\nconst options = { \n key: fs.readFileSync('./server/_.backend.lol.key'), \n cert: fs.readFileSync('./server/_.backend.lol.cer'), \n ca: fs.readFileSync('./ca/ca-backend.cer'), \n requestCert: true, \n rejectUnauthorized: true\n}; \n\nhttps.createServer(options, (req, res) => { \n console.log('Client certificate CN: ', req.socket.getPeerCertificate().subject.CN);\n res.writeHead(200, {\n 'Content-Type': 'application/json'\n }); \n res.end(JSON.stringify({ message: 'Hello World!' }) + \"\\n\"); \n}).listen(8444);\n```\n\nyou can test your new server with\n\n```sh\ncurl --cacert ./ca/ca-backend.cer --cert-type pkcs12 --cert ./client/_.backend.lol.p12:password https://api.backend.lol:8444/\n# will print {\"message\":\"Hello World!\"}\n```\n\n### Otoroshi setup\n\nDownload the latest version of the Otoroshi jar and run it like\n\n```sh\njava -jar otoroshi.jar\n\n[info] otoroshi-env - Admin API exposed on http://otoroshi-api.oto.tools:8080\n[info] otoroshi-env - Admin UI exposed on http://otoroshi.oto.tools:8080\n[info] otoroshi-in-memory-datastores - Now using InMemory DataStores\n[info] otoroshi-env - The main datastore seems to be empty, registering some basic services\n[info] otoroshi-env - You can log into the Otoroshi admin console with the following credentials: admin@otoroshi.io / xxxxxxxxxxxx\n[info] play.api.Play - Application started (Prod)\n[info] p.c.s.AkkaHttpServer - Listening for HTTP on /0:0:0:0:0:0:0:0:8080\n[info] p.c.s.AkkaHttpServer - Listening for HTTPS on /0:0:0:0:0:0:0:0:8443\n[info] otoroshi-env - Generating a self signed SSL certificate for https://*.oto.tools ...\n```\n\nand log into otoroshi with the tuple `admin@otoroshi.io / xxxxxxxxxxxx` displayed in the logs. Once logged in, create a new public service exposed on `http://api.frontend.lol` that targets `ahttps://api.backend.lol:8444/`.\n\n@@@ div { .centered-img }\n\n@@@\n\nand test it\n\n```sh\ncurl http://api.frontend.lol:8080/\n# the following error should be returned: {\"Otoroshi-Error\":\"Something went wrong, you should try later. Thanks for your understanding.\"}\n```\n\n@@@ warning\nAs seen before, the target of the otoroshi service is `ahttps://api.backend.lol:8444/`. `ahttps://` is not a typo and is intended. This tells otoroshi to use its experimental `http client` with dynamic tls support to fetch this resource.\n@@@\n\nyou should get an error due to the fact that Otoroshi doesn't know about the server certificate or the client certificate expected by the server.\n\nWe have to add the client certificate for `https://api.backend.lol` to Otoroshi. Go to http://otoroshi.oto.tools:8080/bo/dashboard/certificates and create a new item. Copy and paste the content of `./client/_.backend.lol.cer` and `./client/_.backend.lol.key` respectively in `Certificate full chain` and `Certificate private key`.\n\n@@@ div { .centered-img }\n\n@@@\n\nand retry the following curl command \n\n```sh\ncurl http://api.frontend.lol:8080/\n# the output should be: {\"message\":\"Hello World!\"}\n```\n\nnow we have to expose `https://api.frontend.lol:8443` using otoroshi. Go to http://otoroshi.oto.tools:8080/bo/dashboard/certificates and create a new item. Copy and paste the content of `./server/_.frontend.lol.cer` and `./server/_.frontend.lol.key` respectively in `Certificate full chain` and `Certificate private key`.\n\nand try the following command\n\n```sh\ncurl --cacert ./ca/ca-frontend.cer https://api.frontend.lol:8443/\n# the output should be: {\"message\":\"Hello World!\"}\n```\n\nnow we have to enforce the fact that we want client certificate for `api.frontend.lol`. To do that, we have to create a `Validation authority` in otoroshi and use it on the `api.frontend.lol` service. Go to http://otoroshi.oto.tools:8080/bo/dashboard/validation-authorities and create a new item. A validation authority is supposed to be a remote service that will say if the client certificate is valid. Here we don't really care if the certificate is valid or not, but we want to enforce the fact that there is a client certificate. So just check the `All cert. valid button`.\n\n@@@ div { .centered-img }\n\n@@@\n\nnow go back on your `api.frontend.lol` service, in the `Validation authority` section and select the authority you just created.\n\n@@@ div { .centered-img }\n\n@@@\n\nnow if you retry \n\n```sh\ncurl --cacert ./ca/ca-frontend.cer https://api.frontend.lol:8443/\n# the output should be: {\"Otoroshi-Error\":\"You're not authorized here !\"}\n```\n\nyou should get an error because no client cert. is passed with the request. But if you pass the `./client/_.frontend.lol.p12` client cert in your curl call\n\n```sh\ncurl --cacert ./ca/ca-frontend.cer --cert-type pkcs12 --cert ./client/_.frontend.lol.p12:password https://api.frontend.lol:8443/\n# the output should be: {\"message\":\"Hello World!\"}\n```\n\n### End to end test\n\nNow we can try to write a small nodejs client that uses our client certificates. Create a `client.js` file with the following code\n\n```js\nconst fs = require('fs'); \nconst https = require('https'); \n\nprocess.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0;\n\nconst options = { \n hostname: 'api.frontend.lol', \n port: 8443, \n path: '/', \n method: 'GET', \n key: fs.readFileSync('./client/_.frontend.lol.key'), \n cert: fs.readFileSync('./client/_.frontend.lol.cer'), \n ca: fs.readFileSync('./ca/ca-frontend.cer'), \n}; \n\nconst req = https.request(options, (res) => { \n console.log('statusCode', res.statusCode);\n console.log('headers', res.headers);\n console.log('body:');\n res.on('data', (data) => { \n process.stdout.write(data); \n }); \n}); \n\nreq.end(); \n\nreq.on('error', (e) => { \n console.error(e); \n});\n```\n\nand run the following command\n\n```sh\n$ node client.js\n# statusCode 200\n# headers { date: 'Mon, 10 Dec 2018 16:01:11 GMT',\n# connection: 'close',\n# 'transfer-encoding': 'chunked',\n# 'content-type': 'application/json' }\n# body:\n# {\"message\":\"Hello World!\"}\n```\n\nAnd that's it \n\n## Validating client certificates based on user identity\n\n@@@ note { title=\"Experimental Feature\" }\nValidation authorities is an experimental feature. It can change until it becomess an official feature\n@@@\n\nThe use case is the following :\n\n@@@ div { .centered-img }\n\n@@@\n\nthe idea here is to provide a unique client certificate per device that can access Otoroshi and use a validation authority to check if the user is allowed to access the underlying app with a specific device.\n\n### Generate client certificates for devices\n\nTo do that we are going to create two client certificates, one per device (let say for a laptop and a desktop computer). We are going to use the device serial number as common name of the certificate to be able to identify the device behind the certificate.\n\n```sh\nopenssl genrsa -out ./client/device-1.key 2048\nopenssl rsa -in ./client/device-1.key -out ./client/device-1.key\nopenssl req -new -key ./client/device-1.key -out ./client/device-1.csr -subj \"/CN=mbp-123456789\"\nopenssl x509 -req -days 365 -sha256 -in ./client/device-1.csr -CA ./ca/ca-frontend.cer -CAkey ./ca/ca-frontend.key -set_serial 3 -out ./client/device-1\nopenssl pkcs12 -export -clcerts -in client/device-1 -inkey client/device-1.key -out client/device-1.p12\n\nopenssl genrsa -out ./client/device-2.key 2048\nopenssl rsa -in ./client/device-2.key -out ./client/device-2.key\nopenssl req -new -key ./client/device-2.key -out ./client/device-2.csr -subj \"/CN=nuc-987654321\"\nopenssl x509 -req -days 365 -sha256 -in ./client/device-2.csr -CA ./ca/ca-frontend.cer -CAkey ./ca/ca-frontend.key -set_serial 4 -out ./client/device-2\nopenssl pkcs12 -export -clcerts -in client/device-2 -inkey client/device-2.key -out client/device-2.p12\n```\n\n### Setup actual validation\n\nnow we are going to write an validation authority (with mTLS too) that is going to respond on `https://validation.backend.lol:8445`. The server has access to a list of apps, users and devices to check if everything is correct. In this implementation, the lists are hardcoded, but you can write your own implementation that will fetch data from your corporate LDAP, CA, etc. Create a `validation.js` file and add the following content. Don't forget to do `yarn add x509` before running the server with `node validation.js`\n\n```js\nconst fs = require('fs'); \nconst https = require('https'); \nconst x509 = require('x509');\n\n// list of knwon apps\nconst apps = [\n {\n \"id\": \"iogOIDH09EktFhydTp8xspGvdaBq961DUDr6MBBNwHO2EiBMlOdafGnImhbRGy8z\",\n \"name\": \"my-web-service\",\n \"description\": \"A service that says hello\",\n \"host\": \"www.frontend.lol\"\n }\n];\n\n// list of known users\nconst users = [\n {\n \"name\": \"Mathieu\",\n \"email\": \"mathieu@oto.tools\",\n \"appRights\": [\n {\n \"id\": \"iogOIDH09EktFhydTp8xspGvdaBq961DUDr6MBBNwHO2EiBMlOdafGnImhbRGy8z\",\n \"profile\": \"user\",\n \"forbidden\": false\n },\n {\n \"id\": \"PqgOIDH09EktFhydTp8xspGvdaBq961DUDr6MBBNwHO2EiBMlOdafGnImhbRGy8z\",\n \"profile\": \"none\",\n \"forbidden\": true\n },\n ],\n \"ownedDevices\": [\n \"mbp-123456789\",\n \"nuc-987654321\",\n ]\n }\n];\n\n// list of known devices\nconst devices = [\n {\n \"serialNumber\": \"mbp-123456789\",\n \"hardware\": \"Macbook Pro 2018 13 inc. with TouchBar, 2.6 GHz, 16 Gb\",\n \"acquiredAt\": \"2018-10-01\",\n },\n {\n \"serialNumber\": \"nuc-987654321\",\n \"hardware\": \"Intel NUC i7 3.0 GHz, 32 Gb\",\n \"acquiredAt\": \"2018-09-01\",\n },\n {\n \"serialNumber\": \"iphone-1234\",\n \"hardware\": \"Iphone XS, 256 Gb\",\n \"acquiredAt\": \"2018-12-01\",\n }\n];\n\nconst options = { \n key: fs.readFileSync('./server/_.backend.lol.key'), \n cert: fs.readFileSync('./server/_.backend.lol.cer'), \n ca: fs.readFileSync('./ca/ca-backend.cer'), \n requestCert: true, \n rejectUnauthorized: true\n}; \n\nfunction readBody(request) {\n return new Promise((success, failure) => {\n const body = [];\n request.on('data', (chunk) => {\n body.push(chunk);\n }).on('end', () => {\n const bodyStr = Buffer.concat(body).toString();\n success(JSON.parse(bodyStr));\n });\n });\n}\n\nfunction chainIsValid(chain) {\n // validate cert dates\n // validate cert against clr\n // validate whatever you want here\n return true;\n}\n\nfunction call(req, res) {\n readBody(req).then(body => {\n const service = body.service;\n const email = (body.user || { email: 'mathieu@oto.tools' }).email; // here, should not be null if used with an otoroshi auth. module\n // common name should be device serial number\n const commonName = x509.getSubject(body.chain).commonName\n // search for a known device\n const device = devices.filter(d => d.serialNumber === commonName)[0];\n // search for a known user\n const user = users.filter(d => d.email === email)[0];\n // search for a known application\n const app = apps.filter(d => d.id === service.id)[0];\n res.writeHead(200, { 'Content-Type': 'application/json' }); \n if (chainIsValid(body.chain.map(x509.parseCert)) && user && device && app) {\n // check if the user actually owns the device\n const userOwnsDevice = user.ownedDevices.filter(d => d === device.serialNumber)[0];\n // check if the user has rights to access the app\n const rights = user.appRights.filter(d => d.id === app.id)[0];\n const hasRightToUseApp = !rights.forbidden\n if (userOwnsDevice && hasRightToUseApp) {\n // yeah !!!!\n console.log(`Call from user \"${user.email}\" with device \"${device.hardware}\" on app \"${app.name}\" with profile \"${rights.profile}\" authorized`)\n res.end(JSON.stringify({ status: 'good', profile: rights.profile }) + \"\\n\"); \n } else {\n // nope !!! nope, nope nope\n console.log(`Call from user \"${user.email}\" with device \"${device.hardware}\" on app \"${app.name}\" unauthorized because user doesn't owns the hardware or has no rights`)\n res.end(JSON.stringify({ status: 'unauthorized' }) + \"\\n\"); \n }\n } else {\n console.log(`Call unauthorized`)\n res.end(JSON.stringify({ status: 'unauthorized' }) + \"\\n\"); \n }\n });\n}\n\nhttps.createServer(options, call).listen(8445);\n```\n\nthe corresponding authority validation can be created in Otoroshi like \n\n```json\n{\n \"id\": \"r7m8j31rh66hhdia3ormfm0wfevu1kvg0zgaxsp3oxb6ivf7fy8kvygmvnrlxv81\",\n \"name\": \"Actual validation authority\",\n \"description\": \"Actual validation authority\",\n \"url\": \"ahttps://validation.backend.lol:8445\",\n \"host\": \"validation.backend.lol\",\n \"goodTtl\": 600000,\n \"badTtl\": 60000,\n \"method\": \"POST\",\n \"path\": \"/certificates/_validate\",\n \"timeout\": 10000,\n \"noCache\": false,\n \"alwaysValid\": false,\n \"headers\": {}\n}\n```\n\nbut you don't need to create it right now.\n\nTypically, a validation authority server is a server with a route on `POST /certificates/_validate` that accepts `application/json` and returns `application/json` with a body like\n\n```json\n{\n \"apikey\": nullable {\n \"clientId\": String,\n \"clientName\": String,\n \"authorizedGroup\": String,\n \"enabled\": Boolean,\n \"readOnly\": Boolean,\n \"allowClientIdOnly\": Boolean,\n \"throttlingQuota\": Long,\n \"dailyQuota\": Long,\n \"monthlyQuota\": Long,\n \"metadata\": Map[String, String]\n },\n \"user\": nullable {\n \"email\": String,\n \"name\": String,\n },\n \"service\": {\n \"id\": String,\n \"name\": String,\n \"groupId\": String,\n \"domain\": String,\n \"env\": String,\n \"subdomain\": String,\n \"root\": String,\n \"metadata\": String\n },\n \"chain\": PemFormattedCertificateChainString,\n \"fingerprints\": Array[String]\n}\n```\n\n\n### Setup Otoroshi\n\nYou can start Otoroshi and import data from the `state.json` file in the demo folder. The login tuple is `admin@otoroshi.io / password`. The `state.json` file contains everything you need for the demo, like certificates, service descriptors, auth. modules, etc ...\n\n```sh\njava -Dapp.importFrom=$(pwd)/state.json -Dapp.privateapps.port=8080 -jar otoroshi.jar\n\n[info] otoroshi-env - Admin API exposed on http://otoroshi-api.oto.tools:8080\n[info] otoroshi-env - Admin UI exposed on http://otoroshi.oto.tools:8080\n[info] otoroshi-in-memory-datastores - Now using InMemory DataStores\n[info] otoroshi-env - The main datastore seems to be empty, registering some basic services\n[info] otoroshi-env - Importing from: /pwd/state.json\n[info] play.api.Play - Application started (Prod)\n[info] otoroshi-env - Successful import !\n[info] p.c.s.AkkaHttpServer - Listening for HTTP on /0:0:0:0:0:0:0:0:8080\n[info] p.c.s.AkkaHttpServer - Listening for HTTPS on /0:0:0:0:0:0:0:0:8443\n```\n\n### Testing \n\nYou can test the service with curl like\n\n```sh\ncurl --cacert ./ca/ca-frontend.cer --cert-type pkcs12 --cert ./client/device-1.p12:password https://www.frontend.lol:8443/\n# output:

Hello World !!!

\ncurl --cacert ./ca/ca-frontend.cer --cert-type pkcs12 --cert ./client/device-2.p12:password https://www.frontend.lol:8443/\n# output:

Hello World !!!

\ncurl --cacert ./ca/ca-frontend.cer --cert-type pkcs12 --cert ./client/_.frontend.lol.p12:password https://www.frontend.lol:8443/\n# output: {\"Otoroshi-Error\":\"You're not authorized here !\"}\n```\n\nas expected, the first two call works as their common name is known by the validation server. The last one fails as it's not known.\n\n### Validate user identity\n\nNow let's try to setup firefox to provide the client certificate. Open firefox settings, go to `privacy settings and security` and click on `display certificates` at the bottom of the page. Here you can add the frontend CA (`./ca/ca-frontend.cer`) in the `Authorities` tab, check the 'authorize this CA to identify websites', and then in the `certificates` tab, import one of the devices `.p12` file (like `./client/device-1.p12`). Firefox will ask for the files password (it should be `password`).\n\n@@@ div { .centered-img }\n\n@@@\n\nNow restart firefox.\n\nNext, go to the `my-web-service` service in otoroshi (log in with `admin@otoroshi.io / password`) and activate `Enforce user login` in the Authentication section. It means that now, you'll have to log in when you'll go to https://www.frontend.lol:8443. With authentication activated on otoroshi, the user identity will be sent to the validation authority, so you can change the following line in the file `validation.js`\n\n```js\nconst email = (body.user || { email: 'mathieu@oto.tools' }).email; // here, should not be null if used with an otoroshi auth. module\n```\n\nto\n\n```js\nconst email = body.user.email;\n```\n\nThen, in Firefox, go to https://www.frontend.lol:8443/, firefox will ask which client certificate to use. Select the one you imported (in the process, maybe firefox will warn you that the certificate of the site is auto signed, just ignore it and continue ;) )\n\n@@@ div { .centered-img }\n\n@@@\n\nthen, you'll see a login screen from otoroshi. You can log in with `mathieu@oto.tools / password` and then you should see the hello world message.\n\n@@@ div { .centered-img }\n\n@@@\n\n### Going further with user authentication\n\nFor stronger user authentication, you can try to use an auth. module baked by a keycloak instance with yubikey as a strong second factor authentication instead of the basic auth. module we used previously in this article.\n"},{"name":"req-transformers.md","id":"/topics/req-transformers.md","url":"/topics/req-transformers.html","title":"Request transformers","content":"# Request transformers\n\nWhen everything has failed and you absolutely need a feature in Otoroshi to make your use case work, there is a solution. Request transformer is an experimental feature hidden behind a feature flag that allow you to code how Otoroshi should behave when receiving and rounting an http request. With request transformers, you can change request / response headers and request / response body to way you want.\n\n@@@ note { title=\"Experimental Feature\" }\nRequest transformers is an experimental feature. It can change until it becomess an official feature\n@@@\n\n@@@ warning { title=\"Use at your own risk\" }\nRequest transformers is a **very** powerful feature that allow you to do almost everything you want. But like any powerful feature, it comes with a price. The fact that you are responsible for how the request is transformed makes you responsible for all issues introduced by the transformer. If you block the current thread, introduce big latency, generate uncatched exceptions, etc. it's **your fault**. You have to ensure that your code is fast, non blocking, safe, etc. In any case, Otoroshi developers will not responsible for issues caused by a request transformer.\n\nAnd always remember this quote from ~~uncle Ben~~ Winston Churchill:\n\n

\"Where there is great power there is great responsibility\"

\n@@@\n\n## Enabling request transformers\n\nFirst you have to enable Otoroshi request transformers with the `-Dotoroshi.scripts.enabled=true` flag, or using env. variable `OTOROSHI_SCRIPTS_ENABLED=true`.\n\n## Anatomy of a transformer\n\nA request transformer is a piece of Scala code than can handle the complete lifecycle of an http request made on Otoroshi\n\n```scala\ncase class HttpRequest(url: String, method: String, headers: Map[String, String], query: Map[String, String], cookies: Seq[WSCookie] = Seq.empty[WSCookie]) {\n lazy val host: String = headers.getOrElse(\"Host\", \"\")\n lazy val uri: Uri = Uri(url)\n lazy val scheme: String = uri.scheme\n lazy val authority: Uri.Authority = uri.authority\n lazy val fragment: Option[String] = uri.fragment\n lazy val path: String = uri.path.toString()\n lazy val queryString: Option[String] = uri.rawQueryString\n lazy val relativeUri: String = uri.toRelative.toString()\n}\ncase class HttpResponse(status: Int, headers: Map[String, String], cookies: Seq[WSCookie] = Seq.empty[WSCookie])\n\ntrait RequestTransformer {\n\n /**\n * See RequestTransformer.transformRequest\n */\n def transformRequestSync(\n snowflake: String, // a cluster unique request id\n rawRequest: HttpRequest, // the http request as received by Otoroshi\n otoroshiRequest: HttpRequest, // the http request modified by Otoroshi, ready to be sent to the target service\n desc: ServiceDescriptor, // the service description for the request \n apiKey: Option[ApiKey] = None, // the api key used, if one\n user: Option[PrivateAppsUser] = None // the user, if one\n )(implicit \n env: Env, // the otoroshi environnment\n ec: ExecutionContext, // the threadpool for the request to perform async actions\n mat: Materializer // the akka materializer to work with streams\n ): Either[Result, HttpRequest] = {\n Right(otoroshiRequest)\n }\n\n /**\n * Transform the request forwarded from Otoroshi to the target service\n */\n def transformRequest(\n snowflake: String, // a cluster unique request id\n rawRequest: HttpRequest, // the http request as received by Otoroshi\n otoroshiRequest: HttpRequest, // the http request modified by Otoroshi, ready to be sent to the target service\n desc: ServiceDescriptor, // the service description for the request \n apiKey: Option[ApiKey] = None, // the api key used, if one\n user: Option[PrivateAppsUser] = None // the user, if one\n )(implicit \n env: Env, // the otoroshi environnment\n ec: ExecutionContext, // the threadpool for the request to perform async actions\n mat: Materializer // the akka materializer to work with streams\n ): Future[Either[Result, HttpRequest]] = { // return a future of Either. On the left side, an http error if there was en err. On the Right side, the transformed http request\n FastFuture.successful(transformRequestSync(snowflake, rawRequest, otoroshiRequest, desc, apiKey, user)(env, ec, mat))\n }\n\n /**\n * See RequestTransformer.transformResponse\n */\n def transformResponseSync(\n snowflake: String, // a cluster unique request id\n rawResponse: HttpResponse, // the http response as received by Otoroshi from the target service\n otoroshiResponse: HttpResponse, // the http response modified by Otoroshi, ready to be sent back to the client\n desc: ServiceDescriptor, // the service description for the request \n apiKey: Option[ApiKey] = None, // the api key used, if one\n user: Option[PrivateAppsUser] = None // the user, if one\n )(implicit \n env: Env, // the otoroshi environnment\n ec: ExecutionContext, // the threadpool for the request to perform async actions\n mat: Materializer // the akka materializer to work with streams\n ): Either[Result, HttpResponse] = {\n Right(otoroshiResponse)\n }\n\n /**\n * Transform the response from the target to the client\n */ \n def transformResponse(\n snowflake: String, // a cluster unique request id\n rawResponse: HttpResponse, // the http response as received by Otoroshi from the target service\n otoroshiResponse: HttpResponse, // the http response modified by Otoroshi, ready to be sent back to the client\n desc: ServiceDescriptor, // the service description for the request \n apiKey: Option[ApiKey] = None, // the api key used, if one\n user: Option[PrivateAppsUser] = None // the user, if one\n )(implicit \n env: Env, // the otoroshi environnment\n ec: ExecutionContext, // the threadpool for the request to perform async actions\n mat: Materializer // the akka materializer to work with streams\n ): Future[Either[Result, HttpResponse]] = { // return a future of Either. On the left side, an http error if there was en err. On the Right side, the transformed http response\n FastFuture.successful(transformResponseSync(snowflake, rawResponse, otoroshiResponse, desc, apiKey, user)(env, ec, mat))\n }\n\n /**\n * Transform the request body forwarded from Otoroshi to the target service\n * It uses akka-stream (see https://doc.akka.io/docs/akka/2.5/index.html)\n */\n def transformRequestBody(\n snowflake: String, // a cluster unique request id\n body: Source[ByteString, _], // the body of the request, a stream of byte array\n rawRequest: HttpRequest, // the http request as received by Otoroshi\n otoroshiRequest: HttpRequest, // the http request modified by Otoroshi, ready to be sent to the target service\n desc: ServiceDescriptor, // the service description for the request \n apiKey: Option[ApiKey] = None, // the api key used, if one\n user: Option[PrivateAppsUser] = None // the user, if one\n )(implicit \n env: Env, // the otoroshi environnment\n ec: ExecutionContext, // the threadpool for the request to perform async actions\n mat: Materializer // the akka materializer to work with streams\n ): Source[ByteString, _] = { // return a stream of byte array\n body\n }\n\n /**\n * Transform the response body forwarded from target to the client. \n * It uses akka-stream (see https://doc.akka.io/docs/akka/2.5/index.html)\n */\n def transformResponseBody(\n snowflake: String, // a cluster unique request id\n body: Source[ByteString, _], // the body of the response, a stream of byte array\n rawResponse: HttpResponse, // the http response as received by Otoroshi from the target service\n otoroshiResponse: HttpResponse, // the http response modified by Otoroshi, ready to be sent back to the client\n desc: ServiceDescriptor, // the service description for the request \n apiKey: Option[ApiKey] = None, // the api key used, if one\n user: Option[PrivateAppsUser] = None // the user, if one\n )(implicit \n env: Env, // the otoroshi environnment\n ec: ExecutionContext, // the threadpool for the request to perform async actions\n mat: Materializer // the akka materializer to work with streams\n ): Source[ByteString, _] = {\n body\n }\n}\n```\n\nfor more information about APIs you can use\n\n* https://www.playframework.com/documentation/2.6.x/api/scala/index.html#package\n* https://www.playframework.com/documentation/2.6.x/api/scala/index.html#play.api.mvc.Results\n* https://github.com/MAIF/otoroshi\n* https://doc.akka.io/docs/akka/2.5/stream/index.html\n* https://doc.akka.io/api/akka/current/akka/stream/index.html\n* https://doc.akka.io/api/akka/current/akka/stream/scaladsl/Source.html\n\n## Writing a transformer from Otoroshi UI\n\nLog into Otoroshi and go to `Settings (cog icon) / Scripts`. Here you can create multiple request transformer scripts and associate it with service descriptor later.\n\n@@@ div { .centered-img }\n\n@@@\n\nwhen you write an instance of a transformer in the Otoroshi UI, do the following\n\n```scala\nimport akka.stream.Materializer\nimport env.Env\nimport models.{ApiKey, PrivateAppsUser, ServiceDescriptor}\nimport otoroshi.script._\nimport play.api.Logger\nimport play.api.mvc.{Result, Results}\nimport scala.util._\nimport scala.concurrent.{ExecutionContext, Future}\n\nclass MyTransformer extends RequestTransformer {\n\n val logger = Logger(\"my-transformer\")\n\n // implements the methods you want\n}\n\n// WARN: do not forget this line to provide a working instance of your transformer to Otoroshi\nnew MyTransformer()\n```\n\nYou can use the compile button to check if the script compiles, or code the transformer in your IDE (see next point).\n\nThen go to a service descriptor, scroll to the bottom of the page, and select your transformer in the list\n\n@@@ div { .centered-img }\n\n@@@\n\n## Providing a transformer from Java classpath\n\nYou can write your own transformer using your favorite IDE. Just create an SBT project with the following dependencies. It can be quite handy to manage the source code like any other piece of code, and it avoid the compilation time for the script at Otoroshi startup.\n\n```scala\nlazy val root = (project in file(\".\")).\n settings(\n inThisBuild(List(\n organization := \"com.example\",\n scalaVersion := \"2.12.7\",\n version := \"0.1.0-SNAPSHOT\"\n )),\n name := \"request-transformer-example\",\n resolvers += Resolver.bintrayRepo(\"maif\", \"maven\"),\n libraryDependencies += \"fr.maif.otoroshi\" %% \"otoroshi\" % \"1.x.x\"\n )\n```\n\nWhen your code is ready, create a jar file \n\n```\nsbt package\n```\n\nand add the jar file to the Otoroshi classpath\n\n```sh\njava -Dotoroshi.scripts.enabled=true -cp \"/path/to/transformer.jar:$/path/to/otoroshi.jar\" play.core.server.ProdServerStart\n```\n\nthen, in your service descriptor, you can chose your transformer in the list. If you want to do it from the API, you have to defined the transformerRef using `cp:` prefix like \n\n```json\n{\n \"transformerRef\": \"cp:my.class.package.MyTransformer\"\n}\n```\n\n## Getting custom configuration from the Otoroshi config. file\n\nLet say you need to provide custom configuration values for a script, then you can customize a configuration file of Otoroshi\n\n```hocon\ninclude \"application.conf\"\n\notoroshi {\n scripts {\n enabled = true\n }\n}\n\nmy-transformer {\n env = \"prod\"\n maxRequestBodySize = 2048\n maxResponseBodySize = 2048\n}\n```\n\nthen start Otoroshi like\n\n```sh\njava -Dconfig.file=/path/to/custom.conf -jar otoroshi.jar\n```\n\nthen, in your transformer, you can write something like \n\n```scala\npackage com.example.otoroshi\n\nimport akka.stream.Materializer\nimport akka.stream.scaladsl._\nimport akka.util.ByteString\nimport env.Env\nimport models.{ApiKey, PrivateAppsUser, ServiceDescriptor}\nimport otoroshi.script._\nimport play.api.Logger\nimport play.api.mvc.{Result, Results}\nimport scala.util._\nimport scala.concurrent.{ExecutionContext, Future}\n\nclass BodyLengthLimiter extends RequestTransformer {\n\n override def transformResponseBody(\n snowflake: String,\n body: Source[ByteString, _],\n rawResponse: HttpResponse,\n otoroshiResponse: HttpResponse,\n desc: ServiceDescriptor,\n apiKey: Option[ApiKey],\n user: Option[PrivateAppsUser])(implicit env: Env, ec: ExecutionContext, mat: Materializer): Source[ByteString, _] = {\n val max = env.configuration.getOptional[Long](\"my-transformer.maxResponseBodySize\").getOrElse(Long.MaxValue)\n body.limitWeighted(max)(_.size)\n }\n\n override def transformRequestBody(\n snowflake: String,\n body: Source[ByteString, _],\n rawRequest: HttpRequest,\n otoroshiRequest: HttpRequest,\n desc: ServiceDescriptor,\n apiKey: Option[ApiKey],\n user: Option[PrivateAppsUser])(implicit env: Env, ec: ExecutionContext, mat: Materializer): Source[ByteString, _] = {\n val max = env.configuration.getOptional[Long](\"my-transformer.maxRequestBodySize\").getOrElse(Long.MaxValue)\n body.limitWeighted(max)(_.size)\n }\n}\n```\n\n## Using a library that is not embedded in Otoroshi\n\nJust use the `classpath` option when running Otoroshi\n\n```sh\njava -Dotoroshi.scripts.enabled=true -cp \"/path/to/library.jar:$/path/to/otoroshi.jar\" play.core.server.ProdServerStart\n```\n\nBe carefull as your library can conflict with other libraries used by Otoroshi and affect its stability\n"},{"name":"service-mesh.md","id":"/topics/service-mesh.md","url":"/topics/service-mesh.html","title":"Service mesh with Otoroshi","content":"# Service mesh with Otoroshi\n\n[Service mesh](http://philcalcado.com/2017/08/03/pattern_service_mesh.html) is a pattern that gained a lot of traction lately with tools like [Kubernetes](https://kubernetes.io/) and [Istio](https://istio.io/) using patterns like [sidecar container](https://kubernetes.io/blog/2015/06/the-distributed-system-toolkit-patterns/#example-1-sidecar-containers). It has the advantages to move the complexity of calling other services outside the application. For the application, it's just a local call and the proxy handles apikeys, throttling, quotas, resilience, tracing, etc ...\n\n## Architecture\n\nLet's say that we have 4 service that want to talk to each other. But we don't want to handle the complexity of calling those services with their own apikey, etc ...\n\n@@@ div { .centered-img }\n\n@@@\n\nSo we are going to build an infrastructure where each service has a sidecar Otoroshi that will handle the calls to other services\n\n@@@ div { .centered-img }\n\n@@@\n\n## Configuration\n\nIn this infrastructure, each service has its own service descriptor in Otoroshi that points to another Otoroshi instance. The challenge here is to dynamically change the target of any service when the current call is supposed to access th actual app.\n\nTo do that, each Otoroshi instance (that is part of the same cluster and use the same database) will have a configuration slightly different configuration than the other ones. \n\n```hocon\nsidecar {\n serviceId = \"my-local-service-id\" # the id of the local sidecar app\n target = \"http://127.0.0.1:56876\" # the local service target, it should be on 127.0.0.1 but can be on another ip address\n from = \"127.0.0.1\" # the ip address authorized to actualy access the local app, it should be on 127.0.0.1 but can be on another ip address\n strict = true # use actual remote address or remote address/proxied remote address\n apikey {\n clientId = \"my-apikey-id\" # the apikey client id to access other services\n }\n}\n```\n\n## Configuration using env. variables\n\nYou can also only env. variables to configure sidecar Otoroshi instances \n\n```sh\nSIDECAR_SERVICE_ID=my-local-service-id\nSIDECAR_TARGET=http://127.0.0.1:56876\nSIDECAR_FROM=127.0.0.1\nSIDECAR_STRICT=true\nSIDECAR_APIKEY_CLIENT_ID=my-apikey-id\n```\n\n## Example\n\nYou can find a full example of a simple service mesh using Otoroshi [here](https://github.com/MAIF/otoroshi/tree/master/demos/service-mesh)."},{"name":"snow-monkey.md","id":"/topics/snow-monkey.md","url":"/topics/snow-monkey.html","title":"Chaos engineering with the Snow Monkey","content":"# Chaos engineering with the Snow Monkey\n\nNihonzaru (the Snow Monkey) is the chaos engineering tool provided by Otoroshi. You can access it at `Settings (cog icon) / Snow Monkey`.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Chaos engineering\n\nOtoroshi offers some tools to introduce [chaos engineering](https://principlesofchaos.org/) in your everyday life. With chaos engineering, you will improve the resilience of your architecture by creating faults in production on running systems. With [Nihonzaru (the snow monkey)](https://en.wikipedia.org/wiki/Japanese_macaque) Otoroshi helps you to create faults on http request/response handled by Otoroshi. \n\n@@@ div { .centered-img }\n\n@@@\n\n## Settings\n\n@@@ div { .centered-img }\n\n@@@\n\nThe snow monkey let you define a few settings to work properly :\n\n* **Include user facing apps.**: you want to create fault in production, but maybe you don't want your users to enjoy some nice snow monkey generated error pages. Using this switch let you include of not user facing apps (ui apps). Each service descriptor has a `User facing app switch` that will be used by the snow monkey.\n* **Dry run**: when dry run is enabled, outages will be registered and will generate events and alerts (in the otoroshi eventing system) but requests won't be actualy impacted. It's a good way to prepare applications to the snow monkey habits\n* **Outage strategy**: Either `AllServicesPerGroup` or `OneServicePerGroup`. It means that only one service per group or all services per groups will have `n` outages (see next bullet point) during the snow monkey working period\n* **Outages per day**: during snow monkey working period, each service per group or one service per group will have only `n` outages registered \n* **Working period**: the snow monkey only works during a working period. Here you can defined when it starts and when it stops\n* **Outage duration**: here you can defined the bounds for the random outage duration when an outage is created on a service\n* **Impacted groups**: here you can define a list of service groups impacted by the snow monkey. If none is specified, then all service groups will be impacted\n\n## Faults\n\nWith the snow monkey, you can generate four types of faults\n\n* **Large request fault**: Add trailing bytes at the end of the request body (if one)\n* **Large response fault**: Add trailing bytes at the end of the response body\n* **Latency injection fault**: Add random response latency between two bounds\n* **Bad response injection fault**: Create predefined responses with custom headers, body and status code\n\nEach fault let you define a ratio for impacted requests. If you specify a ratio of `0.2`, then 20% of the requests for the impacte service will be impacted by this fault\n\n@@@ div { .centered-img }\n\n@@@\n\nThen you juste have to start the snow monkey and enjoy the show ;)\n\n@@@ div { .centered-img }\n\n@@@\n\n## Current outages\n\nIn the last section of the snow monkey page, you can see current outages (per service), when they started, their duration, etc ...\n\n@@@ div { .centered-img }\n\n@@@"},{"name":"ssl.md","id":"/topics/ssl.md","url":"/topics/ssl.html","title":"SSL/TLS termination with Otoroshi","content":"# SSL/TLS termination with Otoroshi\n\nOtoroshi can be used as an SSL/TLS termination. It is enabled by default but you can customise HTTPS port with `https.port` config. and env. var `HTTPS_PORT`. You can create upload any certificate you want in the Otoroshi UI or using the API. Just go to `settings (cog icon) / SSL certificates`.\n\n@@@ note { title=\"Experimental Feature\" }\nDynamic SSL/TLS termination is an experimental feature. It can change until it becomess an official feature\n@@@\n\n@@@ note { title=\"TLS 1.3 support\" }\nOtoroshi does support TLS 1.3 when used in combination with JDK 11\n\n\n@@@\n\n@@@ div { .centered-img }\n\n@@@\n\nHere you can add your own certificates, your own CA and even create self signed certificates or certificates from CAs. You can enable auto renewal of thoses self signed certificates or certificates generated. Certificates have to be created with the certificate chain and the private key in PEM format with no password on the private key.\n\nYou can remove the password of a key with the following command\n\n```sh\nopenssl rsa -in keywithpassword.key -out keywithoutpassword.key\n```\n\n@@@ div { .centered-img }\n\n@@@\n\n"},{"name":"1-groups.md","id":"/usage/1-groups.md","url":"/usage/1-groups.html","title":"Managing service groups","content":"# Managing service groups\n\nGo to `settings (cog icon) / All service groups` to access the list of service groups.\n\n@@@ div { .centered-img }\n\n@@@\n\nAnd you should see the list of existing `Service groups`.\n\n@@@ div { .centered-img }\n\n@@@\n\nBut what is a `Service group` anyway ?\n\n## Otoroshi entities\n\nThere are 3 major entities at the core of Otoroshi :\n\n* **service groups**\n* service descriptors\n* api keys\n\n@@@ div { .centered-img }\n\n@@@\n\nA `service group` is just some kind of logical container for `service descriptors`. A `service group` also has some `api keys` assigned that will be used to access all the `service descriptors` contained in the `service group`.\n\n## Create a service group\n\nA `service group` is a really simple structure with an `id`, a name and a description. To create a new one, just click on the `Add item` button.\n\n@@@ div { .centered-img }\n\n@@@\n\nmodify the name and the description of the group\n\n@@@ div { .centered-img }\n\n@@@\n\nand click on `Create group`\n\n@@@ div { .centered-img }\n\n@@@\n\nThen, you should find your brand new `Service group` in the list of `Service groups`\n\n@@@ div { .centered-img }\n\n@@@\n\n## Update a service\n\nTo update a `Service group`, just click on the edit button of your `Service group`\n\n@@@ div { .centered-img }\n\n@@@\n\nUpdate the name and description of the `Service group` and click on the `Update group` button to validate name update.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Delete a service group\n\nTo delete a `Service group`, just click on the delete button of your `Service group`\n\n@@@ div { .centered-img }\n\n@@@\n\nFinally confirm the command\n\n@@@ div { .centered-img }\n\n@@@\n"},{"name":"2-services.md","id":"/usage/2-services.md","url":"/usage/2-services.html","title":"Managing services","content":"# Managing services\n\nNow let's create services. Services or `service descriptor` let you declare how to proxy a call from a domain name to another domain name (or multiple domain names). Let's say you have an API exposed on `http://192.168.0.42` and I want to expose it on `https://my.api.foo`. Otoroshi will proxy all calls to `https://my.api.foo` and forward them to `http://192.168.0.42`. While doing that, it will also log everyhting, control accesses, etc.\n\n## Otoroshi entities\n\nThere are 3 major entities at the core of Otoroshi\n\n* service groups\n* **service descriptors**\n* api keys\n\n@@@ div { .centered-img }\n\n@@@\n\nA `service descriptor` is contained in a `service group` and is allowed to be accessed by all the `api key`s authorized on the `service group`.\n\n## Create a service descriptor\n\nTo create a `service descriptor`, click on `Add service` on the Otoroshi sidebar. Then you will be asked to choose a name for the service what is the group of the service. You also have two buttons to create a new group and assign it to the service and create a new group with a name based on the service name.\n\nYou will have a serie of toggle buttons to\n\n* activate / deactivate a service\n* display maintenance page for a service\n* display contruction page for a service\n* enable otoroshi custom response headers containing request id, latency, etc \n* force https usage on the exposed service\n\nThen, you will be able to choose the URL that will be used to reach your new service on Otoroshi.\n\n@@@ div { .centered-img #service-flags }\n\n@@@\n\nIn the `service targets` section, you will be able to choose where the call will be forwarded. You can use multiple targets, in that case, Otoroshi will perform a round robin load balancing between the targets. If the `override Host header` toggle is one, the host header will be changed for the host of the target. For example, if you request `http://www.oto.tools/api` with a target to `http://www-internal.service.local/api`, the target will receive a `Host: www-internal.service.local` instead of `Host: www.oto.tools`.\n\nYou can also specify a target root, if you say that the target root is `/foo/`, then any call to `https://my.api.foo` will call `http://192.168.0.42/foo/` and nay call to `https://my.api.foo/bar` will call `http://192.168.0.42/foo/bar`.\n\nIn the URL patterns section, you will be able to choose, URL by URL which is private and which is public. By default, all services are private and each call must provide an `api key`. But sometimes, you need to access a service publicly. In that case, you can provide patterns (regex) to make some or all URL public (for example with the pattern `/.*`). You also have a `private pattern` field to restrict public patterns.\n\n@@@ div { .centered-img #targets }\n\n@@@\n\n### Otoroshi exchange protocol\n\nIf you enable secure communication for a given service, you will have to add a filter on the target application that will take the `Otoroshi-State` header and return it in a header named `Otoroshi-State-Resp`. Otoroshi is also sending a `JWT token`in a header named `Otoroshi-Claim` that the target app can validate.\n\n@@@ div { .centered-img }\n\n@@@\n\nThe `Otoroshi-Claim` is a JWT token containing some informations about the service that is called and the client if available. \n\nBy default, the otoroshi jwt token is signed with the `app.claim.sharedKey` config property (or using the `$CLAIM_SHAREDKEY` env. variable) and uses the `HMAC512` signing algorythm. But it is possible to customize how the token is signed from the service descriptor page in the `Otoroshi exchange protocol` section. \n\n@@@ div { .centered-img }\n\n@@@\n\nusing another signing algo.\n\n@@@ div { .centered-img }\n\n@@@\n\nhere you can choose the signing algorithm and the secret/keys used. You can use syntax like `${env.MY_ENV_VAR}` or `${config.my.config.path}` to provide secret/keys values. \n\nFor example, for a service named `my-service` with a signing key `secret` with `HMAC512` signing algorythm, the basic JWT token that will be sent should look like the following\n\n```\neyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiItLSIsImF1ZCI6Im15LXNlcnZpY2UiLCJpc3MiOiJPdG9yb3NoaSIsImV4cCI6MTUyMTQ0OTkwNiwiaWF0IjoxNTIxNDQ5ODc2LCJqdGkiOiI3MTAyNWNjMTktMmFjNy00Yjk3LTljYzctMWM0ODEzYmM1OTI0In0.mRcfuFVFPLUV1FWHyL6rLHIJIu0KEpBkKQCk5xh-_cBt9cb6uD6enynDU0H1X2VpW5-bFxWCy4U4V78CbAQv4g\n```\n\nif you decode it, the payload will look something like\n\n```json\n{\n \"sub\": \"apikey_client_id\",\n \"aud\": \"my-service\",\n \"iss\": \"Otoroshi\",\n \"exp\": 1521449906,\n \"iat\": 1521449876,\n \"jti\": \"71025cc19-2ac7-4b97-9cc7-1c4813bc5924\"\n}\n```\n\nIf you want to validate the `Otoroshi-Claim` on the target app side to ensure that the input requests only comes from `Otoroshi`, you will have to write an HTTP filter to do the job. For instance, if you want to write a filter to make sure that requests only comes from Otoroshi, you can write something like the following (using playframework 2.6).\n\nScala\n: @@snip [filter.scala](../snippets/filter.scala)\n\nJava\n: @@snip [filter.java](../snippets/filter.java)\n\n\n### Canary mode\n\nOtoroshi provides a feature called `Canary mode`. It lets you define new targets for a service, and route a percentage of the traffic on those targets. It's a good way to test a new version of a service before public release. As any client need to be routed to the same version of targets any time, Otoroshi will issue a special header and a cookie containing a `session id`. The header is named `Otoroshi-Canary-Id`.\n\n@@@ div { .centered-img }\n\n@@@\n\n### Service health check\n\nOtoroshi is also capable of checking the health of a service. You can define a URL that will be tested, and Otoroshi will ping that URL regularly. Will doing so, Otoroshi will pass a numeric value in a header named `Otoroshi-Health-Check-Logic-Test`. You can respond with a header named `Otoroshi-Health-Check-Logic-Test-Result` that contains the value of `Otoroshi-Health-Check-Logic-Test` + 42 to indicate that the service is working properly.\n\n@@@ div { .centered-img }\n\n@@@\n\n### Service circuit breaker\n\nIn Otoroshi, each service has its own client settings with a circuit breaker and some retry capabilities. In the `Client settings` section, you will be able to customize the client's behavior.\n\n@@@ div { .centered-img }\n\n@@@\n\n### Service settings\n\nYou can also provide some additionnal information about a given service, like an `Open API` descriptor, some metadata, a list of whitelisted/blacklisted ip addresses, etc.\n\nHere you can also define some headers that will be added to each request to the targets. And you will be able to define headers to route the call only if the defined header is present on the request.\n\n@@@ div { .centered-img #service-meta }\n\n@@@\n\n### Read only\n\nThe read only flag on a service descriptor means that this service can only be used with `HEAD`, `OPTIONS` and `GET` http verbs. You can also active the same flag on `ApiKey`s to be more specific on who cannot use write http verbs.\n\n### CORS \n\nIf you enabled this section, CORS will be automatically supported on the current service provider. The pre-flight request will be handled by Otoroshi. You can customize every CORS headers :\n\n@@@ div { .centered-img }\n\n@@@\n\n### Service authentication\n\nSee @ref:[Aauthentication](./9-auth.md)\n\n### Custom error templates\n\nFinally, you can define custom error templates that will be displayed when an error occurs when Otoroshi try to reach the target or when Otoroshi itself has an error. You can also define custom templates for maintenance and service pages.\n"},{"name":"3-apikeys.md","id":"/usage/3-apikeys.md","url":"/usage/3-apikeys.html","title":"Managing API keys","content":"# Managing API keys\n\nNow that you know how to create service groups and service descriptors, we will see how to create API keys.\n\n## Otoroshi entities\n\nThere are 3 major entities at the core of Otoroshi.\n\n* service groups\n* service descriptors\n* **api keys**\n\n@@@ div { .centered-img }\n\n@@@\n\nAn `API key` related to a `service group` to allow you to access any `service descriptor` contained in a `service group`. You can, of course, create multiple `API key` for a given `service group`.\n\nIn the Otoroshi admin dashboard, we chose to access `API keys` from `service descriptors` only, but when you access `API keys` for a `service descriptor`, you actually access `API keys` for the `service group` containing the `service descriptor`.\n\n`API keys` can be provided to Otoroshi through :\n\n* `Otoroshi-Authorization: Basic $base64(client_id:client_secret)` header, in that case, the `Otoroshi-Authorization` header will **not** be sent to the target. `Basic ` is optional.\n* `Authorization: Basic $base64(client_id:client_secret)` header, in that case, the `Authorization` header **will** be sent to the target\n* `Otoroshi-Token: Bearer $jwt_token` where the JWT token has been signed with the `API key` client secret, in that case, the `Otoroshi-Token` header will **not** be sent to the target. `Bearer ` is optional.\n* `Authorization: Bearer $jwt_token` where the JWT token has been signed with the `API key` client secret, in that case, the `Authorization` header **will** be sent to the target\n* `Cookie: access_token=$jwt_token;` where the JWT token has been signed with the `API key` client secret, in that case, the cookie named `access_token` **will** be sent to the target\n* `Otoroshi-Client-Id` and `Otoroshi-Client-Secret` headers, in that case the `Otoroshi-Client-Id` and `Otoroshi-Client-Secret` headers will not be sent to the target.\n\n## List API keys for a service descriptor\n\nGo to a service descriptor using `All services` quick link in the sidebar or the search box.\n\n@@@ div { .centered-img }\n\n@@@\n\nSelect a `service descriptor`.\n\n@@@ div { .centered-img }\n\n@@@\n\nClick on `API keys` in the sidebar\n\n@@@ div { .centered-img }\n\n@@@\n\nYou should see the list of API keys for that `service descriptor`\n\n@@@ div { .centered-img }\n\n@@@\n\n## Create an API key for a service descriptor\n\n@@@ div { .centered-img }\n\n@@@\n\nYou can add a name for your new API key, you can also change client's id and client's secret. You can also configure the throttling rate of the API key (calls per second), and the authorized number of call per day and per month. You may also activate or de-activate the api key from that screen.\n\nInformations about current quotas usage will be returned in response headers.\n\n* `Otoroshi-Daily-Calls-Remaining` : authorized calls remaining for this day\n* `Otoroshi-Monthly-Calls-Remaining` : authorized calls remaining for this month\n* `Otoroshi-Proxy-Latency` : latency induced by Otoroshi\n* `Otoroshi-Upstream-Latency` : latency between Otoroshi and target\n\n@@@ div { .centered-img #quotas }\n\n@@@\n\n@@@ warning\nDaily and monthly quotas are based on the following rules :\n\n* daily quota is computed between 00h00:00.000 and 23h59:59.999\n* monthly qutoas is computed between the first day of the month at 00h00:00.000 and the last day of the month at 23h59:59.999\n@@@\n\n## Update an API key\n\nTo update an `API key`, just click on the edit button of your `API key`\n\n@@@ div { .centered-img }\n\n@@@\n\nUpdate the name, secret, state and quotas (if needed) of the `API key` and click on the `Update API key` button\n\n@@@ div { .centered-img }\n\n@@@\n\n## Delete an API key\n\nTo delete an `API key`, just click on the delete button of your `API key`\n\n@@@ div { .centered-img }\n\n@@@\n\nand confirm the command\n\n@@@ div { .centered-img }\n\n@@@\n\n### Read only\n\nThe read only flag on an `ApiKey` this apikey can only use allowed services with `HEAD`, `OPTIONS` and `GET` http verbs.\n\n## Use a JWT token to pass an API key\n\nYou can use a JWT token to pass an API key to Otoroshi. \nYou can use `Otoroshi-Authorization: Bearer $jwt_token`, `Authorization: Bearer $jwt_token` header and `Cookie: access_token=$jwt_token;` to pass the JWT token.\nYou have to create a JWT token with a signing algorythm that can be `HS256` or `HS512`. Then you have to provide an `iss` claim with the value of your API key `clientId` and sign the JWT token with your API key `clientSecret`.\n\nFor example, with an API key like `clientId=abcdef` and `clientSecret=1234456789`, your JWT token should look like\n\n```json\n{\n \"alg\": \"HS256\",\n \"typ\": \"JWT\"\n}\n{\n \"iss\":\"abcdef\",\n \"name\": \"John Doe\",\n \"admin\": true\n}\n```\n\nin that case, when you sign the token with the secret of the API key `1234456789`, the signature will be `_eancnYCD3makSSox2v2xErjNYkRtcX558QiJGCbino`, resulting in a encoded JWT header like\n\n```\neyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.\neyJpc3MiOiJhYmNkZWYiLCJuYW1lIjoiSm9obiBEb2UiLCJhZG1pbiI6dHJ1ZX0.\n_eancnYCD3makSSox2v2xErjNYkRtcX558QiJGCbino\n```\n"},{"name":"4-monitor.md","id":"/usage/4-monitor.md","url":"/usage/4-monitor.html","title":"Monitoring services","content":"# Monitoring services\n\nOnce you have declared services, you can monitor them with Otoroshi.\n\n@@@ warning\nYou have to use [Elastic](https://www.elastic.co) to enable analytics features in Otoroshi\n@@@\n\nOnce you have setup @ref:[Otoroshi events push to an elastic cluster](../integrations/analytics.md) (through webhooks, kafka, or elastic integration) you can setup Otoroshi events read from an elastic cluster. Go to `settings (cog icon) / Danger Zone` and expand the `Analytics: Elastic dashboard datasource (read)` section.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Service healthcheck\n\nIf you have defined an health check URL in the service descriptor, you can access the health check page from the sidebar of the service page.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Service live stats\n\nYou can also monitor live stats like total of served request, average response time, average overhead, etc. The live stats page can be accessed from the sidebar of the service page.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Service analytics\n\nYou can also get some aggregated metrics. The analytics page can be accessed from the sidebar of the service page.\n\n@@@ div { .centered-img }\n\n@@@\n"},{"name":"5-sessions.md","id":"/usage/5-sessions.md","url":"/usage/5-sessions.html","title":"Managing sessions","content":"# Managing sessions\n\nWith Otoroshi you can manage sessions of connected users and you can discard sessions whenever you want. Session last 24h by default and you can customize them with `app.backoffice.session.exp` and `app.privateapps.session.exp` @ref:[config keys](../firstrun/configfile.md)\n\n## Admin. sessions\n\nTo see last current admin session on Otoroshi from the UI, go to `settings (cog icon) / Admins sessions`. Here you can discard individual sessions or all sessions at once using `Discard session` and `Discard all sessions` buttons.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Private apps. session\n\nTo see last current admin session on Otoroshi from the UI, go to `settings (cog icon) / Priv. apps sessions`. Here you can discard individual sessions or all sessions at once using `Discard session` and `Discard all sessions` buttons.\n\n@@@ div { .centered-img }\n\n@@@\n"},{"name":"6-audit.md","id":"/usage/6-audit.md","url":"/usage/6-audit.html","title":"Auditing Otoroshi","content":"# Auditing Otoroshi\n\nWith Otoroshi, any admin action and any sucpicious/alert action is recorded. These records are stored in Otoroshi's datastore (only the last n records, defined by the `app.events.maxSize` @ref:[config key](../firstrun/configfile.md)). All the records can be send through the analytics mechanism (WebHook, Kafka, Elastic) for external and/or further usage. We recommand sending away those records for security reasons.\n\n@@@ warning\nYou have to use [Elastic](https://www.elastic.co) to enable analytics features in Otoroshi. See @ref:[Elastic setup section](../integrations/analytics.md)\n@@@\n\n## Audit trail\n\nTo see last `app.events.maxSize` admin actions on Otoroshi from the UI, go to `settings (cog icon) / Audit log`.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Alerts\n\nTo see last `app.events.maxSize` alerts on Otoroshi from the UI, go to `settings (cog icon) / Alerts log`.\n\n@@@ div { .centered-img }\n\n@@@\n\n## List of possible alerts\n\n```\nMaxConcurrentRequestReachedAlert\nCircuitBreakerOpenedAlert\nCircuitBreakerClosedAlert\nSessionDiscardedAlert\nSessionsDiscardedAlert\nPanicModeAlert\nOtoroshiExportAlert\nU2FAdminDeletedAlert\nBlackListedBackOfficeUserAlert\nAdminLoggedInAlert\nAdminFirstLogin\nAdminLoggedOutAlert\nDbResetAlert\nDangerZoneAccessAlert\nGlobalConfigModification\nRevokedApiKeyUsageAlert\nServiceGroupCreatedAlert\nServiceGroupUpdatedAlert\nServiceGroupDeletedAlert\nServiceCreatedAlert\nServiceUpdatedAlert\nServiceDeletedAlert\nApiKeyCreatedAlert\nApiKeyUpdatedAlert\nApiKeyDeletedAlert\n```\n"},{"name":"7-metrics.md","id":"/usage/7-metrics.md","url":"/usage/7-metrics.html","title":"Otoroshi global metrics","content":"# Otoroshi global metrics\n\nOtoroshi provide some global metrics about services usage. Go to `settings (cog icon) / Global Ananlytics`\n\n@@@ warning\nYou have to use [Elastic](https://www.elastic.co) to enable analytics features in Otoroshi. See @ref:[Elastic setup section](../integrations/analytics.md)\n@@@\n\n@@@ div { .centered-img }\n\n@@@\n"},{"name":"8-importsexports.md","id":"/usage/8-importsexports.md","url":"/usage/8-importsexports.html","title":"Import and export","content":"# Import and export\n\nWith Otoroshi you can easily save the current state of the proxy and restore it later. Go to `settings (cog icon) / Danger Zone` and scroll to the bottom of the page\n\n## Full export\n\nClick on the `Full export` button.\n\n@@@ div { .centered-img }\n\n@@@\n\nYour browser will start to download a JSON file containing the internal state of your Otoroshi cluster.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Full import\n\nIf you want to restore an export, go to `settings (cog icon) / Danger Zone` and scroll to the bottom of the page. Click on the `Recover from full export file` button\n\n@@@ div { .centered-img }\n\n@@@\n\nChoose export file on your system.\n\n@@@ div { .centered-img }\n\n@@@\n\nClick on the `Flush datastore and import ...` button, confirm and you will be logged out.\n\n@@@ div { .centered-img }\n\n@@@\n"},{"name":"9-auth.md","id":"/usage/9-auth.md","url":"/usage/9-auth.html","title":"Authentication","content":"# Authentication\n\nYou can create auth. configuration in Otoroshi. Just go to `settings (cog icon) / Global auth. configs`.\n\n## OAuth 2\n\nCreate a new `Generic oauth2 provider` config and customize the following informations:\n\n```json\n{\n \"clientId\": \"xxxx\",\n \"clientSecret\": \"xxxx\",\n \"authorizeUrl\": \"http://yourOAuthServer/oauth/authorize\",\n \"tokenUrl\": \"http://yourOAuthServer/oauth/token\",\n \"userInfoUrl\": \"http://yourOAuthServer/userinfo\",\n \"loginUrl\": \"http://yourOAuthServer/login\",\n \"logoutUrl\": \"http://yourOAuthServer/logout?redirectQueryParamName=${redirect}\",\n \"accessTokenField\": \"access_token\",\n \"nameField\": \"name\",\n \"emailField\": \"email\",\n \"callbackUrl\": \"http://privateapps.oto.tools/privateapps/generic/callback\"\n}\n```\n\nIf used for BackOffice authentication, the callback url should be `http://otoroshi.oto.tools/backoffice/auth0/callback`.\n\nFor `logoutUrl`, `redirectQueryParamName` is a parameter with a name specific to your OAuth2 provider (for example, in Auth0, this parameter is called `returnTo`, in Kecloak it is called `redirect_uri`).\n\nif you are using a [KeyCloak](https://www.keycloak.org/) server, you can configure it this way, assuming you are using the master realm and you created a new client with a client secret, callback urls set to `http://privateapps.oto.tools/*`.\n\n```json\n{\n \"clientId\": \"clientId\",\n \"clientSecret\": \"clientSecret\",\n \"authorizeUrl\": \"http://keycloakHost/auth/realms/master/protocol/openid-connect/auth\",\n \"tokenUrl\": \"http://keycloakHost/auth/realms/master/protocol/openid-connect/token\",\n \"userInfoUrl\": \"http://keycloakHost/auth/realms/master/protocol/openid-connect/userinfo\",\n \"loginUrl\": \"http://keycloakHost/auth/realms/master/protocol/openid-connect/auth\",\n \"logoutUrl\": \"http://keycloakHost/auth/realms/master/protocol/openid-connect/logout?redirect_uri=${redirect}\",\n \"accessTokenField\": \"access_token\",\n \"nameField\": \"name\",\n \"emailField\": \"email\",\n \"callbackUrl\": \"http://privateapps.oto.tools/privateapps/generic/callback\"\n}\n```\n\n## Ldap\n\nCreate a new `Ldap auth. provider` config and customize the following informations:\n\n```json\n{\n \"serverUrl\": \"ldap://ldap.forumsys.com:389\",\n \"searchBase\": \"dc=example,dc=com\",\n \"groupFilter\": \"ou=chemists\",\n \"searchFilter\": \"(mail=${username})\",\n \"adminUsername\": \"cn=read-only-admin,dc=example,dc=com\",\n \"adminPassword\": \"password\",\n \"nameField\": \"cn\",\n \"emailField\": \"mail\"\n}\n```\n\n## In Memory\n\nCreate a new `In memory auth. provider` config and then you will be able to create new users. To set the password, just click on the `Set password` button. It will generate a BCrypt hash of the password you typed.\n\n## Auth0\n\nCreate a new OAuth 2 config and add the following informations:\n\n```json\n{\n \"clientId\": \"yourAuth0ClientId\",\n \"clientSecret\": \"yourAuth0ClientSecret\",\n \"authorizeUrl\": \"https://yourAuth0Domain/authorize\",\n \"tokenUrl\": \"https://yourAuth0Domain/oauth/token\",\n \"userInfoUrl\": \"https://yourAuth0Domain/userinfo\",\n \"loginUrl\": \"https://yourAuth0Domain/authorize\",\n \"logoutUrl\": \"https://yourAuth0Domain/v2/logout?returnTo=${redirect}\",\n \"accessTokenField\": \"access_token\",\n \"nameField\": \"name\",\n \"emailField\": \"email\",\n \"otoroshiDataField\": \"app_metadata | otoroshi_data\",\n \"callbackUrl\": \"http://privateapps.oto.tools/privateapps/generic/callback\"\n}\n```\n\nIf you enable Otoroshi exchange protocol, the JWT xill have the following fields (all optional)\n\n* `email`\n* `name`\n* `picture`\n* `user_id`\n* `given_name`\n* `family_name`\n* `gender`\n* `locale`\n* `nickname`\n\nIn Auth0, the metadata is a flat object placed in the `profile / http://yourdomain/app_metadata / otoroshi_data`. You might need to write an Auth0 rule to copy app metadata under `http://yourdomain/app_metadata`, the `http://yourdomain/app_metadata` value is a config property `app.appMeta`. The rule could be something like the following\n\n```js\nfunction (user, context, callback) {\n var namespace = 'http://yourdomain/';\n context.idToken[namespace + 'user_id'] = user.user_id;\n context.idToken[namespace + 'user_metadata'] = user.user_metadata;\n context.idToken[namespace + 'app_metadata'] = user.app_metadata;\n callback(null, user, context);\n}\n```"},{"name":"index.md","id":"/usage/index.md","url":"/usage/index.html","title":"Using Otoroshi","content":"# Using Otoroshi\n\nNow we will see how to use Otoroshi for basic tasks that will be useful for your day to day work with Otoroshi.\n\n@@@ index\n\n* [create group](./1-groups.md)\n* [create service](./2-services.md)\n* [create API Keys](./3-apikeys.md)\n* [monitor service](./4-monitor.md)\n* [sessions management](./5-sessions.md)\n* [Audit trail and alerts](./6-audit.md)\n* [Global metrics](./7-metrics.md)\n* [Exports and imports](./8-importsexports.md)\n* [Authentication](./9-auth.md)\n\n@@@\n"},{"name":"videos.md","id":"/videos.md","url":"/videos.html","title":"Video tutorials","content":"# Video tutorials\n\n## Create a service restricted by an api key\n\nhere you will learn how to create a simple service that will proxy an existing api and how to secure it using an api key.\n\n\n\n## Use CLI to create load balanced services with retry\n\nhere you will learn how to create a service using the Otoroshi CLI and how to use load balancing with retry features of Otoroshi.\n\n"}] \ No newline at end of file diff --git a/docs/manual/css/fonts/icons.eot b/docs/manual/css/fonts/icons.eot new file mode 100644 index 0000000000..a5203e8766 Binary files /dev/null and b/docs/manual/css/fonts/icons.eot differ diff --git a/docs/manual/css/fonts/icons.svg b/docs/manual/css/fonts/icons.svg new file mode 100644 index 0000000000..58c7488236 --- /dev/null +++ b/docs/manual/css/fonts/icons.svg @@ -0,0 +1,12 @@ + + + +Generated by Fontastic.me + + + + + + + + diff --git a/docs/manual/css/fonts/icons.ttf b/docs/manual/css/fonts/icons.ttf new file mode 100644 index 0000000000..6da4b13e05 Binary files /dev/null and b/docs/manual/css/fonts/icons.ttf differ diff --git a/docs/manual/css/fonts/icons.woff b/docs/manual/css/fonts/icons.woff new file mode 100644 index 0000000000..2c7e917bb9 Binary files /dev/null and b/docs/manual/css/fonts/icons.woff differ diff --git a/docs/manual/css/page.css b/docs/manual/css/page.css new file mode 100644 index 0000000000..599d297061 --- /dev/null +++ b/docs/manual/css/page.css @@ -0,0 +1,968 @@ +@charset "UTF-8"; + +/* fonts */ + +@font-face { + font-family: "icons"; + src:url("fonts/icons.eot"); + src:url("fonts/icons.eot?#iefix") format("embedded-opentype"), + url("fonts/icons.woff") format("woff"), + url("fonts/icons.ttf") format("truetype"), + url("fonts/icons.svg#icons") format("svg"); + font-weight: normal; + font-style: normal; +} + +/* body */ + +body { + background: #fff; + padding: 0; + margin: 0; + font-weight: 400; + font-style: normal; + line-height: 1.4em; + position: relative; + cursor: default; + font: 1em "Roboto", "Helvetica Neue", "Helvetica", Arial, sans-serif; + color: #333333; + font-smoothing: antialiased; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-font-size-adjust: none; + text-rendering: optimizeLegibility; +} + +/* headers */ + +h1 { + font-family: inherit; + font-size: 2.6em; + font-weight: 300; + color: #333333; + margin-bottom: 0.4em; + margin-top: 1.1em; +} + +h1:first-child { + margin-top: 0; +} + +h2 { + font-family: inherit; + font-size: 1.8em; + font-weight: 400; + color: #333333; + margin-bottom: 0.2em; + margin-top: 1.3em; +} + +h2:first-child { + margin-top: 0; +} + +h3 { + font-family: inherit; + font-size: 1.125em; + font-weight: 700; + color: #333333; + margin-top: 1.3em; +} + +h3:first-child { + margin-top: 0; +} + +h4 { + font-family: inherit; + font-size: 1em; + font-weight: 700; + color: #333333; + margin-top: 1.3em; +} + +h4:first-child { + margin-top: 0; +} + +h5 { + font-family: inherit; + font-size: 1em; + color: #333333; + margin-top: 1.3em; +} + +h5:first-child { + margin-top: 0; +} + +h6 { + font-family: inherit; + font-size: 1em; + color: #333333; + margin-top: 1.3em; +} + +h6:first-child { + margin-top: 0; +} + +/* text */ + +p { + font-family: inherit; + font-size: 1em; + color: #333333; + line-height: 1.45em; + margin: 1em 0em; +} + +/* links */ + +a { + font-family: inherit; + color: #4078c0; + text-decoration: none; + cursor: pointer; +} + +a:link { + color: #4078c0; +} + +a:visited { + color: #4078c0; +} + +a:hover { + color: #4078c0; + text-decoration: underline; +} + +a:active { + color: #4078c0; +} + +/* header anchors */ + +a.anchor .anchor-link:before { + content: "§"; + font-size: 18px; + font-family: "icons" !important; + font-style: normal !important; + font-weight: normal !important; + font-variant: normal !important; + text-transform: none !important; + speak: none; + line-height: 1.2; + vertical-align: middle; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +h1 > a.anchor, h2 > a.anchor, h3 > a.anchor, h4 > a.anchor, h5 > a.anchor, h6 > a.anchor { + visibility: hidden; + position: absolute; + display: block; + width: 30px; + margin-left: -30px; + padding-right: 3px; + text-align: right; + text-decoration: none; +} + +h1:hover > a.anchor, h2:hover > a.anchor, h3:hover > a.anchor, h4:hover > a.anchor, h5:hover > a.anchor, h6:hover > a.anchor { + visibility: visible; + color: inherit; + text-decoration: none; +} + + +/* table */ + +table th { + background: #ffffff; + border: solid 1px #dddddd; +} + +table td { + background: #ffffff; + border: solid 1px #dddddd; +} + +/* code */ + +pre { + padding: 1rem !important; + border: 1px solid #dddddd !important; + margin: 0 0 1rem 0 !important; + white-space: pre; + overflow: auto; + line-height: 0.9em !important; +} + +code { + line-height: 1.45em !important; + font-family: Consolas, "Liberation Mono", Courier, monospace; + font-size: 0.85em !important; + font-weight: normal !important; + padding: 0 !important; +} + +pre > code { + background: none; + border: none; +} + +p code { + border: none; + color: #0c323b; + padding: 0 0.2em !important; +} + +li code { + border: none; + color: #0c323b; + padding: 0 0.2em !important; +} + +/* Github-like */ + +pre.prettyprint span.str { + color: #183691; +} +pre.prettyprint span.kwd { + color: #000000; + font-weight: bold; +} +pre.prettyprint span.com { + color: #999988; + font-style: italic; +} +pre.prettyprint span.typ { + color: #445588; + font-weight: bold; +} +pre.prettyprint span.lit { + color: #009999 +} +pre.prettyprint span.pun { + color: #000000; + font-weight: bold; +} +pre.prettyprint span.pln { + color: #000000 +} +pre.prettyprint span.tag { + color: navy +} +pre.prettyprint span.atn { + color: teal +} +pre.prettyprint span.atv { + color: #dd1144 +} +pre.prettyprint span.dec { + color: #990000 +} +pre.prettyprint span.var { + color: #000000 +} +pre.prettyprint span.fun { + color: #990000 +} + +/* tabbed code snippets */ + +dl.tabbed { + position: relative; +} + +dl.tabbed dt { + float: left; + display: inline-block; + position: relative; + left: 1px; + margin-left: -1px; +} + +dl.tabbed dt.current { + z-index: 2; +} + +dl.tabbed dt a { + display: block; + border: 1px solid #f7f7f7; + padding: 0 20px; + height: 30px; + line-height: 2; + color: #4078c0; + text-decoration: none; +} + +dl.tabbed dt a:hover { + background: #f7f7f7; +} + +dl.tabbed dt.first a { + border-top-left-radius: 5px; +} + +dl.tabbed dt.last a { + border-top-right-radius: 5px; +} + +dl.tabbed dt.current a { + background-color: #f7f7f7; + border-color: #dddddd; + border-bottom: 0; + padding-bottom: 1px; +} + +dl.tabbed dt.current a { + color: #0c323b; + outline: none; +} + +dl.tabbed dd { + position: absolute; + width: 100%; + left: 0; + top: 29px; +} + +dl.tabbed dd.current { + z-index: 1; +} + +dl.tabbed dd pre { + border-top-left-radius: 0 !important; +} + +dl.tabbed dd.has-note pre { + border-bottom: 0 !important; + border-bottom-left-radius: 0 !important; + border-bottom-right-radius: 0 !important; + margin-bottom: 0 !important; +} + +dl.tabbed dd.has-note blockquote { + color: #0c323b; + border: solid #dddddd; + border-width: 1px 1px 1px 10px; + border-bottom-left-radius: 5px; + border-bottom-right-radius: 5px; + margin-top: 0; + font-style: italic; +} + +/* blockquotes */ + +blockquote { + border-left: 4px solid rgb(221, 221, 221); + margin: 1.5em 0; + padding: 1em 20px; + color: rgb(119, 119, 119); + line-height: 1.4em; +} + +blockquote p { + margin: 0; + line-height: 1.4em; +} + +blockquote p ~ p { + margin-top: 1em; +} + +blockquote p code { + background: #fff; + padding-right: 0.4em !important; +} + +/* callout */ + +.callout { + border: none; + border-left: 10px solid #4078c0; + background-color: rgba(64, 120, 192, 0.1); +} + +.callout.warning { + border-color: #DB504A; + background-color: #fff3d9; +} + +.callout .callout-title { + font-weight: bold; + margin-bottom: 0.33em; +} + +.callout div ~ p { + margin-top: 0; +} + +.callout p ~ p { + margin-top: 1em; + margin-bottom: 0; +} + +.callout p code { + background: #fff; + padding-right: 0.4em !important; +} + +/* footnotes */ + +small { + display: block; +} + +/* logo links */ + +.logo .svg-icon-logo { + height: 24px; + width: 112px; +} + +.logo:hover { + text-decoration: none; +} + +.svg-icon-logo .svg-icon-logo-text { + fill: #ffffff; +} + +.logo:hover .svg-icon-logo .svg-icon-logo-text { + fill: #4078C0; +} + +/* site header */ + +.site-header { + background: #fcfcfc; + border-bottom: solid 1px #cccccc; +} + +.site-header .title { + display: inline-block; + float: left; + height: 70px; + line-height: 70px; + font-weight: 400; + font-size: 1.4em; +} + +.site-header .title a:link, .site-header .title a:visited { + color: #515151; +} + +.site-header .title a:hover { + color: #515151; + text-decoration: none; +} + +.site-header .off-canvas-toggle { + float: left; +} + +.site-header .off-canvas-toggle .svg-icon-menu { + height: 40px; + width: 40px; + margin-top: 1rem; + margin-right: 1rem; +} + +.site-header .off-canvas-toggle .svg-icon-menu .svg-icon-menu-path { + fill: #000; +} + +.site-header .off-canvas-toggle .svg-icon-menu:hover .svg-icon-menu-path { + fill: #4183C4; +} + +.site-header .logo { + float: right; + margin-top: 1rem; + text-align: right; + color: #000; +} + +/* page header */ + +.page-header { + background: #ffffff; + margin-top: 1rem; +} + +.page-header .nav-breadcrumbs { + border-bottom: 1px solid #c2d2dc; +} + +.page-header .nav-breadcrumbs ul { + display: block; + overflow: hidden; + margin: 0; + padding: 15px; + list-style: none; + font-size: 0.9em; +} + +.page-header .nav-breadcrumbs li { + float: left; + margin: 0; +} + +.page-header .nav-breadcrumbs li:before { + content: "/"; + margin: 0 5px; + color: #c2d2dc; +} + +.page-header .nav-breadcrumbs li:first-child:before { + content: ""; + margin: 0; +} + +.page-header .nav-breadcrumbs li a { + color: #4078c0; + font-weight: normal; +} + +/* content */ + +.site-content .row { + max-width: 90rem; +} + +.page-content { + background: #ffffff; + min-height: 600px; + padding: 2.5rem 0; +} + +/* sidebar */ + +.sidebar { + background: #ffffff; + width: 100%; + padding: 0; +} + +.sidebar .nav-toc { + padding-top: 1rem; +} + +/* navigation */ + +.nav-home { + background: #f7f7f7; + line-height: 2rem; +} + +.nav-home a { + display: block; + color: #4078c0; + font-weight: bold; + font-size: 1rem; + padding-left: 1rem; +} + +.nav-home a.active { + background: #e7e7e7; +} + +.nav-home a:hover { + background: #e7e7e7; + text-decoration: none; +} + +.nav-home a .home-icon { + font-size: 18px; + font-family: "icons" !important; + font-style: normal !important; + font-weight: normal !important; + font-variant: normal !important; + text-transform: none !important; + speak: none; + line-height: 1.2; + vertical-align: middle; + padding-right: 5px; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.nav-home .version-number { + padding-left: 1.4rem; + font-size: 0.9rem; +} + +.nav-toc { +} + +.nav-toc ul { + margin: 0; + padding: 0; + line-height: 2rem; +} + +.nav-toc ul > li { + list-style-type: none; + padding-left: 0; +} + +.nav-toc ul > li a { + display: block; + color: #4078c0; + font-weight: bold; + font-size: 0.9rem; + padding-left: 1rem; +} + +.nav-toc ul > li a.active { + background: #e7e7e7; +} + +.nav-toc ul > li a:hover { + background: #e7e7e7; + text-decoration: none; +} + +.nav-toc ul > li > ul li { + margin-top: 0; + border-top: 0; + padding-top: 0; + padding-bottom: 0; +} + +.nav-toc ul > li > ul li a { + font-size: 0.9rem; + font-weight: normal; + text-transform: none; + margin-top: 0; + padding-left: 1.2rem; +} + +.nav-toc ul > li > ul > li > ul li a { + font-size: 0.8rem; + padding-left: 2rem; +} + + +/* site navigation */ + +.site-nav { + margin-top: 1rem; + margin-bottom: 1rem; + border: solid 1px #cccccc; +} + +/* off-canvas navigation colours */ + +.off-canvas { +} + +.off-canvas-nav { + padding: 0; +} + +.off-canvas-nav .nav-home { + background: #ffffff; +} + +.off-canvas-nav .nav-toc { + background: #ffffff; +} + +.off-canvas-nav .nav-toc ul > li { +} + +.off-canvas-nav .nav-toc ul > li a { +} + +.off-canvas-nav .nav-toc ul > li a.active { +} + +.off-canvas-nav .nav-toc ul > li a:hover { +} + +.off-canvas-nav .nav-toc ul > li a.active:hover { +} + +.off-canvas-nav .nav-toc ul > li > ul li { + border-top: 0; +} + +/* page navigation */ + +.page-nav { + background: #ffffff; +} + +.page-nav .nav-title { + display: block; + color: #9eb1b7; + font-weight: bold; + font-size: 0.8rem; + padding-left: 1rem; +} + +.page-nav .nav-toc { + background: #ffffff; +} + +.page-nav .nav-toc ul > li { + border-top: 0; +} + +.page-nav .nav-toc ul > li a.active { + background: #4078c0; + color: #ffffff; +} + +.page-nav .nav-toc ul > li a.active:hover { + background: #4078c0; +} + +/* navigation next */ + +.nav-next { + background: #F7F7F7; + border-left: 10px solid #4078c0; + margin-top: 40px; + padding: 1em 20px; +} + +.nav-next p { + display: inline; + color: #0c323b; + font-style: italic; +} + +.nav-next a { + color: #4078c0; + font-weight: normal; +} + +/* table of contents -- ordered */ + +.toc a { + color: #4078c0; + font-weight: normal; +} + +.toc ol li { + color: #778a99; +} + +.toc > ol > li > ol { + list-style-type: lower-alpha; +} + +.toc > ol > li > ol > li > ol { + list-style-type: lower-roman; +} + +.toc.main > ol > li { + margin-top: 0.5em; + font-size: 1.2em; +} + +.toc.main > ol > li { + counter-increment: root; +} + +.toc.main > ol > li > ol { + counter-reset: subsection; + list-style-type: none; + margin-left: 2rem; +} + +.toc.main > ol > li > ol > li { + counter-increment: subsection; +} + +.toc.main > ol > li > ol > li:before { + content: counter(root) "." counter(subsection) ". "; + margin-left: -2rem; + padding-right: 0.25rem; +} + +.toc.main > ol > li > ol > li > ol { + list-style-type: lower-alpha; +} + +.toc.main > ol > li > ol > li > ol > li > ol { + list-style-type: lower-roman; +} + +/* table of contents -- unordered */ + +.toc ul li { + color: #778a99; +} + +.toc.main > ul { + margin-left: 0; + padding-bottom: 2rem; + border-bottom: 1px solid #f3f6f6; +} + +.toc.main > ul > li { + margin-top: 2rem; + border-top: 1px solid #f3f6f6; + padding-top: 2rem; + list-style-type: none; +} + +.toc.main > ul > li > a { + font-weight: bold; +} + +.toc.main > ul > li > ul { + margin-top: 1rem; +} + +.toc.main > ul > li > ul > li { + list-style-type: disc; +} + +.toc.main > ul > li > ul > li > a { + font-weight: normal; + text-transform: none; +} + +.toc.main > ul > li > ul > li > ul > li { + list-style-type: circle; +} + +/* site footer */ + +.site-footer { + margin-top: 3rem; + border-top: solid 1px #cccccc; +} + +.site-footer-content.row { + max-width: 90rem; +} + +.site-footer-nav { + padding: 2rem 0; +} + +.site-footer-nav .with-left-divider { + border-top: 0; + border-left: 1px solid rgba(255, 255, 255, 0.2); +} + +@media screen and (max-width: 40em) { + .site-footer-nav .with-left-divider { + margin-top: 2rem; + border-top: 1px solid rgba(255, 255, 255, 0.2); + padding-top: 2rem; + border-left: 0; + } +} + +@media screen and (min-width: 40em) { + .site-footer-nav .nav-links { + height: 8rem; + } + +} +@media screen and (max-width: 511px) { + #search-block { + display: none !important; + } +} + +.nav-links ul { + margin: 0; + padding: 0; + line-height: 2rem; +} + +.nav-links ul > li { + list-style-type: none; + padding-left: 0; +} + +.nav-links ul > li a { + color: #c5d0d4; + font-weight: bold; + font-size: 0.9rem; +} + +.nav-links ul > li a:hover { + color: #4078C0; + text-decoration: none; +} + +.nav-links ul > li > ul li a { + font-weight: normal; + text-transform: none; +} + +.site-footer-base { + padding: 1rem 0; +} + +.site-footer-base h3, .site-footer-base a { +} + +.site-footer-base .copyright { + margin-top: 20px; +} + +.site-footer-base .copyright .text { + display: inline-block; + line-height: 24px; + padding-right: 10px; + vertical-align: top; +} + +.site-footer-base .svg-icon-logo-text { +} + +.site-footer-base .svg-icon-logo-image { +} + +.site-footer-base .logo:hover .svg-icon-logo-text { +} + +.site-footer-base .logo:hover .svg-icon-logo-image { +} + +.source-github { + padding: 10px; + padding-top: 30px; + margin-top: 20px; + border-top: 1px solid #cccccc; +} + +.source-github a { + font-weight: bold; +} + +.title-wrapper { + display: flex; +} + +.title-logo { + content: url("../img/small-otoroshi-logo.png"); + margin-right: 10px; + margin-top: 10px; +} + +p { + text-align: justify; +} + +.centered-img { + width: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} \ No newline at end of file diff --git a/docs/manual/deploy/aws-beanstalk.html b/docs/manual/deploy/aws-beanstalk.html new file mode 100644 index 0000000000..c9c974367f --- /dev/null +++ b/docs/manual/deploy/aws-beanstalk.html @@ -0,0 +1,508 @@ + + + + +AWS - Elastic Beanstalk · Otoroshi + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+ + + +
+
+

AWS - Elastic Beanstalk

+

Now you want to use Otoroshi on AWS. There are multiple options to deploy Otoroshi on AWS, for instance :

+ +

In this section we are going to cover how to deploy Otoroshi on AWS Elastic Beanstalk.

+

AWS Elastic Beanstalk Overview

+

Unlike Clever Cloud, to deploy an application on AWS Elastic Beanstalk, you don’t link your app to your VCS repository, push your code and expect it to be built and run.

+

AWS Elastic Beanstalk does only the run part. So you have to handle your own build pipeline, upload a Zip file containing your runnable, then AWS Elastic Beanstalk will take it from there.

+

Eg: for apps running on the JVM (Scala/Java/Kotlin) a Zip with the jar inside would suffice, for apps running in a Docker container, a Zip with the DockerFile would be enough.

+

Prepare your deployment target

+

Actually, there are 2 options to build your target.

+

Either you create a DockerFile from this Docker image, build a zip, and do all the Otoroshi custom configuration using ENVs.

+

Or you download the otoroshi.jar, do all the Otoroshi custom configuration using your own otoroshi.conf, and create a DockerFile that runs the jar using your otoroshi.conf.

+

For the second option your DockerFile would look like this :

+
FROM openjdk:8
+VOLUME /tmp
+EXPOSE 8080
+ADD otoroshi.jar otoroshi.jar
+ADD otoroshi.conf otoroshi.conf
+RUN sh -c 'touch /otoroshi.jar'
+ENV JAVA_OPTS=""
+ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -Dconfig.file=/otoroshi.conf -jar /otoroshi.jar" ]
+
+

I’d recommend the second option.

+

Now Zip your target (Jar + Conf + DockerFile) and get ready for deployment.

+

Create an Otoroshi instance on AWS Elastic Beanstalk

+

First, go to AWS Elastic Beanstalk Console, don’t forget to sign in and make sure that you are in the good region (eg : eu-west-3 for Paris).

+

Hit Get started

+
+

Specify the Application name of your application, Otoroshi for example.

+
+

Choose the Platform of the application you want to create, in your case use Docker.

+

For Application code choose Upload your code then hit Upload.

+
+

Browse the zip created in the previous section from your machine.

+

As you can see in the image above, you can also choose an S3 location, you can imagine that at the end of your build pipeline you upload your Zip to S3, and then get it from there (I wouldn’t recommend that though).

+

When the upload is done, hit Configure more options.

+
+

Right now an AWS Elastic Beanstalk application has been created, and by default an environment named Otoroshi-env is being created as well.

+

AWS Elastic Beanstalk can manage multiple environments of the same application, for instance environments can be (prod, preprod, expriments…).

+

Otoroshi is a bit particular, it doesn’t make much sense to have multiple environments, since Otoroshi will handle all the requests from/to downstream services regardless of the environment.

+

As you see in the image above, we are now configuring the Otoroshi-env, the one and only environment of Otoroshi.

+

For Configuration presets, choose custom configuration, now you have a load balancer for your environment with the capacity of at least one instance and at most four. I’d recommend at least 2 instances, to change that, on the Capacity card hit Modify.

+
+

Change the Instances to min 2, max 4 then hit Save. For the Scaling triggers, I’d keep the default values, but know that you can edit the capacity config any time you want, it only costs a redeploy, which will be done automatically by the way.

+

Instances size is by default t2.micro, which is a bit small for running Otoroshi, I’d recommend a t2.medium.
On the Instances card hit Modify.

+
+

For Instance type choose t2.medium, then hit Save, no need to change the volume size, unless you have a lot of http call faults, which means a lot more logs, in that case the default volume size may not be enough.

+

The default environment created for Otoroshi, for instance Otoroshi-env, is a web server environment which fits in your case, but the thing is that on AWS Elastic Beanstalk by default a web server environment for a docker-based application, runs behind an Nginx proxy. We have to remove that proxy. So on the Software card hit Modify.

+
+

For Proxy server choose None then hit Save.

+

Also note that you can set Envs for Otoroshi in same page (see image below).

+
+

To finalise the creation process, hit Create app on the bottom right.

+

The Otoroshi app is now created, and it’s running which is cool, but we still don’t have neither a datastore nor https.

+

Create an Otoroshi datastore on AWS ElastiCache

+

By default Otoroshi uses non persistent memory to store it’s data, Otoroshi supports many kinds of datastores. In this section we will be covering Redis datastore.

+

Before starting, using a datastore hosted by AWS is not at all mandatory, feel free to use your own if you like, but if you want to learn more about ElastiCache, this section may interest you, otherwise you can skip it.

+

Go to AWS ElastiCache and hit Get Started Now.

+
+

For Cluster engine keep Redis.

+

Choose a Name for your datastore, for instance otoroshi-datastore.

+

You can keep all the other default values and hit Create on the bottom right of the page.

+

Once your Redis Cluster is created, it would look like the image below.

+
+

For applications in the same security group as your cluster, redis cluster is accessible via the Primary Endpoint. Don’t worry the default security group is fine, you don’t need any configuration to access the cluster from Otoroshi.

+

To make Otoroshi use the created cluster, you can either use Envs APP_STORAGE=redis, REDIS_HOST and REDIS_PORT, or set app.storage=redis, app.redis.host and app.redis.port in your otoroshi.conf.

+

Create SSL certificate and configure your domain

+

Otoroshi has now a datastore, but not yet ready for use.

+

In order to get it ready you need to :

+
    +
  • Configure Otoroshi with your domain
  • +
  • Create a wildcard SSL certificate for your domain
  • +
  • Configure Otoroshi AWS Elastic Beanstalk instance with the SSL certificate
  • +
  • Configure your DNS to redirect all traffic on your domain to Otoroshi
  • +
+

Configure Otoroshi with your domain

+

You can use ENVs or you can use a custom otoroshi.conf in your Docker container.

+

For the second option your otoroshi.conf would look like this :

+
   include "application.conf"
+   http.port = 8080
+   app {
+     env = "prod"
+     domain = "mysubdomain.oto.tools"
+     rootScheme = "https"
+     snowflake {
+       seed = 0
+     }
+     events {
+       maxSize = 1000
+     }
+     backoffice {
+       subdomain = "otoroshi"
+       session {
+         exp = 86400000
+       }
+     }
+     
+     storage = "redis"
+     redis {
+        host="myredishost"
+        port=myredisport
+     }
+   
+     privateapps {
+       subdomain = "privateapps"
+     }
+   
+     adminapi {
+       targetSubdomain = "otoroshi-admin-internal-api"
+       exposedSubdomain = "otoroshi-api"
+       defaultValues {
+         backOfficeGroupId = "admin-api-group"
+         backOfficeApiKeyClientId = "admin-client-id"
+         backOfficeApiKeyClientSecret = "admin-client-secret"
+         backOfficeServiceId = "admin-api-service"
+       }
+       proxy {
+         https = true
+         local = false
+       }
+     }
+     claim {
+       sharedKey = "myclaimsharedkey"
+     }
+   }
+   
+   play.http {
+     session {
+       secure = false
+       httpOnly = true
+       maxAge = 2147483646
+       domain = ".mysubdomain.oto.tools"
+       cookieName = "oto-sess"
+     }
+   }
+
+

Create a wildcard SSL certificate for your domain

+

Go to AWS Certificate Manager.

+

Below Provision certificates hit Get started.

+
+

Keep the default selected value Request a public certificate and hit Request a certificate.

+
+

Put your Domain name, use *. for wildcard, for instance *.mysubdomain.oto.tools, then hit Next.

+
+

You can choose between Email validation and DNS validation, I’d recommend DNS validation, then hit Review.

+
+

Verify that you did put the right Domain name then hit Confirm and request.

+
+

As you see in the image above, to let Amazon do the validation you have to add the CNAME record to your DNS configuration. Normally this operation takes around one day.

+

Configure Otoroshi AWS Elastic Beanstalk instance with the SSL certificate

+

Once the certificate is validated, you need to modify the configuration of Otoroshi-env to add the SSL certificate for HTTPS. For that you need to go to AWS Elastic Beanstalk applications, hit Otoroshi-env, then on the left side hit Configuration, then on the Load balancer card hit Modify.

+
+

In the Application Load Balancer section hit Add listener.

+
+

Fill the popup as the image above, then hit Add.

+

You should now be seeing something like this :

+
+

Make sure that your listener is enabled, and on the bottom right of the page hit Apply.

+

Now you have https, so let’s use Otoroshi.

+

Configure your DNS to redirect all traffic on your domain to Otoroshi

+

It’s actually pretty simple, you just need to add a CNAME record to your DNS configuration, that redirects *.mysubdomain.oto.tools to the DNS name of Otoroshi’s load balancer.

+

To find the DNS name of Otoroshi’s load balancer go to AWS Ec2

+

You would find something like this :

+
+

There is your DNS name, so add your CNAME record.

+

Once all these steps are done, the AWS Elastic Beanstalk Otoroshi instance, would now be handling all the requests on your domain. ;)

+ +
+ +
+ +
+
+ +
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + diff --git a/docs/manual/deploy/clevercloud.html b/docs/manual/deploy/clevercloud.html new file mode 100644 index 0000000000..c3dde2cff4 --- /dev/null +++ b/docs/manual/deploy/clevercloud.html @@ -0,0 +1,404 @@ + + + + +Clever Cloud · Otoroshi + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+ + + +
+
+

Clever Cloud

+

Now you want to use Otoroshi on Clever Cloud. Otoroshi has been designed and created to run on Clever Cloud and a lot of choices were made because of how Clever Cloud works.

+

Create an Otoroshi instance on CleverCloud

+

First, fork our project template on Github at https://github.com/MAIF/otoroshi-jar-clevercloud-template.

+

If you want to customize the build script, edit ./clevercloud/build.sh

+

If you want to customize the configuration use env. variables, you can use the example provided below

+

Create a new CleverCloud app based on your fork.

+
+

Then choose what kind of app your want to create, for Otoroshi, choose Java + Jar

+
+

Next, set up choose instance size and auto-scalling. Otoroshi can run on small instances, especially if you just want to test it.

+
+

Finally, choose a name for your app

+
+

Now you just need to customize environnment variables and add the custom build script as pre buid hook :

+

CC_PRE_BUILD_HOOK=./clevercloud/build.sh

+

at this point, you can also add other env. variables to configure Otoroshi like in the example provided below

+
+

You can also use expert mode :

+
+

Now, your app is ready, don’t forget to add a custom domains name on the CleverCloud app matching the Otoroshi app domain. For instance if you used domain names in env. variables like changeme, changeme-admin-internal-api, changeme-api on the cleverapps.io domain, declare changeme.cleverapps.io, changeme-api.cleverapps.io, changeme-admin-internal-api.cleverapps.io.

+

You will find the login/password tuple for first login in the app. logs.

+

Build and deploy Otoroshi from its source code

+

First, fork our project template on Github at https://github.com/MAIF/otoroshi-clevercloud-template.

+

If you want to customize the build script, edit ./clevercloud/build.sh

+

If you want to customize the configuration file, edit ./clevercloud/prod.conf or use env. variables

+

Create a new Clever Cloud app based on your fork.

+
+

Then, you need to choose what kind of app your want to create, for Otoroshi, choose Java or Scala + Play 2

+
+

Then, you will be asked to choose what kind of machine you want to use. M instances are a good choice but you can use a less powerful ones. You can also activate auto-scaling or multi-instances to provie high availibility.

+
+

Then choose a name for your app :

+
+

Now you just need to customize environnment variables and add the custom build script as pre build hook :

+

CC_PRE_BUILD_HOOK=./clevercloud/build.sh

+

at this point, you can also add other env. variables to configure Otoroshi like in the example provided below

+
+

You can also use expert mode :

+
+

Now, your app is ready, don’t forget to add a custom domains name on the CleverCloud app matching the Otoroshi app domain. For instance if you used domain names in env. variables like changeme, changeme-admin-internal-api, changeme-api on the cleverapps.io domain, declare changeme.cleverapps.io, changeme-api.cleverapps.io, changeme-admin-internal-api.cleverapps.io.

+

You will find the login/password tuple for first login in the app. logs.

+

Example of CleverCloud env. variables

+

You can add more env variables to customize your Otoroshi instance like the following. Use the expert mode to copy/paste all the values in one shot :

+
APP_ENV=prod
+APP_STORAGE=inmemory
+APP_DOMAIN=cleverapps.io
+APP_ROOT_SCHEME=https
+APP_BACKOFFICE_SUBDOMAIN=changeme
+ADMIN_API_TARGET_SUBDOMAIN=changeme-admin-internal-api
+ADMIN_API_EXPOSED_SUBDOMAIN=changeme-api
+ADMIN_API_GROUP=psIZ0hI6eAQ2vp7DQoFfdUSfdmamtlkbXwYCe9WQHGBZMO6o5Kn1r2VVSmI61IVX
+ADMIN_API_CLIENT_ID=pWkwudAifrflg8Bh
+ADMIN_API_CLIENT_SECRET=ip53UuY5BFiM3wXkVUhhYrVdbsDYsANCNdRMnW3pU4I268ylsF6xxkvusS6Wv4AW
+ADMIN_API_SERVICE_ID=GVQUWMZHaEYr1tCTNe9CdXOVE4DQnu1VUAx7YyXDlo5XupY3laZlWUnGyDt1vfGx
+CACHE_DEPENDENCIES=true
+CC_PRE_BUILD_HOOK=./clevercloud/build.sh
+CLAIM_SHAREDKEY=Tx1uQXW11pLNlZ25S4A08Uf8HbWDPxZ3KGSSm0B1s90gRk10PNy4d1HKY4Dnvvv5
+ENABLE_METRICS=true
+JAVA_VERSION=8
+PORT=8080
+PLAY_CRYPTO_SECRET=7rNFga4AComd6ey09W9PaHqllLmPHb8WHBhlRe9xjTHOPlN15BCeSQf610cmLU1w
+SESSION_SECURE_ONLY=true
+SESSION_MAX_AGE=259200000
+SESSION_DOMAIN=changeme.cleverapps.io
+SESSION_NAME=otoroshi-session
+USER_AGENT=otoroshi
+
+ +
+ +
+ +
+
+ +
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + diff --git a/docs/manual/deploy/index.html b/docs/manual/deploy/index.html new file mode 100644 index 0000000000..12be251da6 --- /dev/null +++ b/docs/manual/deploy/index.html @@ -0,0 +1,322 @@ + + + + +Deploy to production · Otoroshi + + + + + + + + + + + + + + + +
+
+ + + +
+ + + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + diff --git a/docs/manual/deploy/other.html b/docs/manual/deploy/other.html new file mode 100644 index 0000000000..58a50f67c9 --- /dev/null +++ b/docs/manual/deploy/other.html @@ -0,0 +1,359 @@ + + + + +Others · Otoroshi + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+ + + +
+
+

Others

+

Otoroshi can run wherever you want, even on a raspberry pi (Cluster^^) ;)

+

This section is not finished yet. So, as Otoroshi is available as a Docker image that you can run on any Docker compatible cloud, just go ahead and use it on cloud provider until we have more detailed documentation.

+

Running Otoroshi on AWS Elastic Beanstalk

+

See the dedicated page to run Otoroshi on AWS Elastic Beanstalk

+

Running Otoroshi on Amazon Elastic Container Service

+

Deploy the Docker image using Amazon ECS

+

Running Otoroshi on GCE

+

Deploy the Docker image using Google Compute Engine container integration

+

Running Otoroshi on Azure

+

Deploy the Docker image using Azure Container Service

+

Running Otoroshi on Heroku

+

Deploy the Docker image using Docker integration

+

Running Otoroshi on CloudFoundry

+

Deploy the Docker image using Docker integration

+

Running Otoroshi on your own infrastructure

+

As Otoroshi is a Play Framework application, you can read the doc about putting a Play app in production.

+

https://www.playframework.com/documentation/2.6.x/ProductionConfiguration

+

Download the latest Otoroshi distribution, unzip it, customize it and run it.

+ +
+ +
+ +
+
+ +
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + diff --git a/docs/manual/deploy/scaling.html b/docs/manual/deploy/scaling.html new file mode 100644 index 0000000000..2e9764e8d8 --- /dev/null +++ b/docs/manual/deploy/scaling.html @@ -0,0 +1,584 @@ + + + + +Scaling Otoroshi · Otoroshi + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+ + + +
+
+

Scaling Otoroshi

+

Using multiple instances with a front load balancer

+

Otoroshi has been designed to work with multiple instances. If you already have an infrastructure using frontal load balancing, you just have to declare Otoroshi instances as the target of all domain names handled by Otoroshi

+

Using master / workers mode of Otoroshi

+

You can read everything about it in the clustering section of the documentation.

+

Using IPVS

+

You can use IPVS to load balance layer 4 traffic directly from the Linux Kernel to multiple instances of Otoroshi. You can find example of configuration here

+

Using DNS Round Robin

+

You can use DNS round robin technique to declare multiple A records under the domain names handled by Otoroshi.

+

Using software L4/L7 load balancers

+

You can use software L4 load balancers like NGINX or HAProxy to load balance layer 4 traffic directly from the Linux Kernel to multiple instances of Otoroshi.

+
+
NGINX L7
+
+
upstream otoroshi {
+  server 192.168.1.40:8080 max_fails=1;
+  server 192.168.1.41:8080 max_fails=1;
+  server 192.168.1.42:8080 max_fails=1;
+}
+
+server {
+  listen 80;
+  # http://nginx.org/en/docs/http/server_names.html
+  server_name otoroshi.oto.tools otoroshi-api.oto.tools otoroshi-admin-internal-api.oto.tools privateapps.oto.tools *-api.oto.tools;
+  location / {
+    # SSE config
+    proxy_buffering off;
+    proxy_cache off;
+    proxy_set_header Connection '';
+    proxy_http_version 1.1;
+    chunked_transfer_encoding off;
+  
+    # websockets config
+    proxy_set_header Upgrade $http_upgrade;
+    proxy_set_header Connection "upgrade";
+  
+    # other config
+    proxy_set_header Host $http_host;
+    proxy_set_header X-Real-IP $remote_addr;
+    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+    proxy_set_header X-Forwarded-Proto $scheme;
+    proxy_pass http://otoroshi;
+  }
+}
+
+server {
+  listen 443 ssl;
+  # http://nginx.org/en/docs/http/server_names.html
+  server_name otoroshi.oto.tools otoroshi-api.oto.tools otoroshi-admin-internal-api.oto.tools privateapps.oto.tools *-api.oto.tools;
+  ssl_certificate           /etc/letsencrypt/wildcard.oto.tools/fullchain.pem;
+  ssl_certificate_key       /etc/letsencrypt/wildcard.oto.tools/privkey.pem;
+  ssl_session_cache         shared:SSL:10m;
+  ssl_session_timeout       5m;
+  ssl_prefer_server_ciphers on;
+  ssl_ciphers               ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5;
+  ssl_protocols             TLSv1 TLSv1.1 TLSv1.2;
+  location / {
+    # SSE config
+    proxy_buffering off;
+    proxy_cache off;
+    proxy_set_header Connection '';
+    proxy_http_version 1.1;
+    chunked_transfer_encoding off;
+  
+    # websockets config
+    proxy_set_header Upgrade $http_upgrade;
+    proxy_set_header Connection "upgrade";
+  
+    # other config
+    proxy_set_header Host $http_host;
+    proxy_set_header X-Real-IP $remote_addr;
+    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+    proxy_set_header X-Forwarded-Proto $scheme;
+    proxy_pass http://otoroshi;
+  }
+}
+
NGINX L4
+
+
stream {
+
+  upstream back_http_nodes {
+    zone back_http_nodes 64k;
+    server 192.168.1.40:8080 max_fails=1;
+    server 192.168.1.41:8080 max_fails=1;
+    server 192.168.1.42:8080 max_fails=1;
+  }
+
+  upstream back_https_nodes {
+    zone back_https_nodes 64k;
+    server 192.168.1.40:8443 max_fails=1;
+    server 192.168.1.41:8443 max_fails=1;
+    server 192.168.1.42:8443 max_fails=1;
+  }
+
+  server {
+    listen     80;
+    proxy_pass back_http_nodes;
+    health_check;
+  }
+
+  server {
+    listen     443;
+    proxy_pass back_https_nodes;
+    health_check;
+  }
+  
+}
+
+
HA Proxy L7
+
+
frontend front_nodes_http
+    bind *:80
+    mode http
+    default_backend back_http_nodes
+    timeout client          1m
+
+frontend front_nodes_https
+    bind *:443
+    mode http
+    default_backend back_https_nodes
+    timeout client          1m
+
+backend back_http_nodes
+    mode http
+    balance roundrobin
+    option forwardfor
+    http-request set-header X-Forwarded-Port %[dst_port]
+    http-request add-header X-Forwarded-Proto https if { ssl_fc }
+    http-request set-header X-Client-IP %[src]
+    server node1 192.168.1.40:8080
+    server node2 192.168.1.41:8080
+    server node3 192.168.1.42:8080
+    timeout connect        10s
+    timeout server          1m
+
+backend back_https_nodes
+    mode http
+    balance roundrobin
+    option forwardfor
+    http-request set-header X-Forwarded-Port %[dst_port]
+    http-request add-header X-Forwarded-Proto https if { ssl_fc }
+    http-request set-header X-Client-IP %[src]
+    server node1 192.168.1.40:8443
+    server node2 192.168.1.41:8443
+    server node3 192.168.1.42:8443
+    timeout connect        10s
+    timeout server          1m
+
HA Proxy L4
+
+
frontend front_nodes_http
+    bind *:80
+    mode tcp
+    default_backend back_http_nodes
+    timeout client          1m
+
+frontend front_nodes_https
+    bind *:443
+    mode tcp
+    default_backend back_https_nodes
+    timeout client          1m
+
+backend back_http_nodes
+    mode tcp
+    balance roundrobin
+    server node1 192.168.1.40:8080
+    server node2 192.168.1.41:8080
+    server node3 192.168.1.42:8080
+    timeout connect        10s
+    timeout server          1m
+
+backend back_https_nodes
+    mode tcp
+    balance roundrobin
+    server node1 192.168.1.40:8443
+    server node2 192.168.1.41:8443
+    server node3 192.168.1.42:8443
+    timeout connect        10s
+    timeout server          1m
+
+

Using a custom TCP load balancer

+

You can also use any other TCP load balancer, from a hardware box to a small js file like

+
+
tcp-proxy.js +
+
+
const proxy = require("node-tcp-proxy");
+
+const hosts = ["192.168.1.40", "192.168.1.41", "192.168.1.42"];
+const portsHttp = [8080, 8080, 8080];
+const portsHttps = [8443, 8443, 8443];
+
+const proxyHttp = proxy.createProxy(80, hosts, portsHttp, {
+  tls: false
+});
+
+const proxyHttps = proxy.createProxy(443, hosts, portsHttps, {
+  tls: false
+});
+
+
tcp-proxy.rs +
+
+
extern crate futures;
+extern crate tokio;
+extern crate rand;
+
+use futures::{Future, Stream};
+use rand::Rng;
+use std::net::SocketAddr;
+use tokio::io::copy;
+use tokio::net::{TcpListener, TcpStream};
+use tokio::prelude::*;
+
+fn main() -> Result<(), Box<std::error::Error>> {
+    let urls: Vec<std::net::SocketAddr> = vec![
+        std::net::SocketAddr::new("192.168.1.40".parse().unwrap(), 8080),
+        std::net::SocketAddr::new("192.168.1.41".parse().unwrap(), 8080),
+        std::net::SocketAddr::new("192.168.1.42".parse().unwrap(), 8080),
+    ];
+    let addr: SocketAddr = "0.0.0.0:80".to_string().parse().unwrap();
+    let sock = TcpListener::bind(&addr).unwrap();
+    println!("TCP load balancer listening on {}", addr);
+    let done = sock
+        .incoming()
+        .map_err(move |e| println!("Error accepting socket; error = {:?}", e))
+        .for_each(move |server_socket| {
+            let index = rand::thread_rng().gen_range(0, urls.len());
+            let url = &urls[index];
+            let client_pair = TcpStream::connect(&url).map(|socket| socket.split());
+            let msg = client_pair
+                .and_then(move |(client_reader, client_writer)| {
+                    let (server_reader, server_writer) = server_socket.split();
+                    let upload = copy(server_reader, client_writer);
+                    let download = copy(client_reader, server_writer);
+                    upload.join(download)
+                }).then(|_res| {
+                    Ok(())
+                });
+            tokio::spawn(msg);
+            Ok(())
+        });
+    tokio::run(done);
+    Ok(())
+}
+
+ +
+ +
+ +
+
+ +
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + diff --git a/docs/manual/embedding.html b/docs/manual/embedding.html new file mode 100644 index 0000000000..44a77d18a9 --- /dev/null +++ b/docs/manual/embedding.html @@ -0,0 +1,518 @@ + + + + +Embedding Otoroshi · Otoroshi + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+ + + +
+
+

Embedding Otoroshi

+

Otoroshi provides an API to start Otoroshi instances programmatically from any JVM app.

+

Getting the dependencies

+

You can get the Otoroshi dependency from bintray

+
+
Sbt
+
+
resolvers += Resolver.bintrayRepo("maif", "maven")
+
+libraryDependencies += "fr.maif.otoroshi" %% "otoroshi" % "1.4.18"
+libraryDependencies += "fr.maif.otoroshi" %% "otoroshi" % "1.4.18" classifier "assets"
+
Gradle
+
+
repositories {
+  bintray.repo(repoOwner: 'maif', repoName: 'maven')
+}
+
+dependencies {
+  compile group: 'fr.maif.otoroshi', name: 'otoroshi_2.12', version: '1.4.18'
+  compile group: 'fr.maif.otoroshi', name: 'otoroshi_2.12', version: '1.4.18', classifier 'assets'
+}
+
+

Starting Otoroshi

+

Now just instanciate an Otoroshi proxy with the configuration you like and you will be able to control it using the internal APIs of Otoroshi.

+
+
Scala
+
+
package myapp
+
+import java.nio.file.Files
+import models.{ServiceDescriptor, Target, ApiKey, ServiceGroup}
+import com.typesafe.config.ConfigFactory
+import otoroshi.api.Otoroshi
+import play.core.server.ServerConfig
+
+object MyApp extends App {
+
+  val rootDir = Files.createTempDirectory("otoroshi-embed").toFile
+
+  val descriptor = ServiceDescriptor(
+    id = "embed-test",
+    name = "embed-test",
+    env = "prod",
+    subdomain = "api",
+    domain = "oto.tools",
+    targets = Seq(
+      Target(
+        host = s"127.0.0.1:8080",
+        scheme = "http"
+      )
+    ),
+    forceHttps = false,
+    enforceSecureCommunication = false,
+    publicPatterns = Seq("/.*")
+  )
+
+  val apiKey = ApiKey(
+    clientId = "1234",
+    clientSecret = "1234567890",
+    clientName = "test-key",
+    enabled = true,
+    authorizedGroup = "embed-group"
+  )
+
+  val group = ServiceGroup(
+    id = "embed-group",
+    name = "Embed group"
+  )
+
+  val otoroshi = Otoroshi(
+    ServerConfig(
+      address = "0.0.0.0",
+      port = Some(8888),
+      rootDir = rootDir
+    ),
+    ConfigFactory.parseString(s"""
+      |app {
+      |  storage = "leveldb"
+      |  importFrom = "./my-state.json"
+      |  env = "prod"
+      |  adminapi {
+      |    targetSubdomain = "otoroshi-admin-internal-api"
+      |    exposedSubdomain = "otoroshi-api"
+      |    defaultValues {
+      |      backOfficeGroupId = "admin-api-group"
+      |      backOfficeApiKeyClientId = "admin-api-apikey-id"
+      |      backOfficeApiKeyClientSecret = "admin-api-apikey-secret"
+      |      backOfficeServiceId = "admin-api-service"
+      |    }
+      |  }
+      |  claim {
+      |    sharedKey = "mysecret"
+      |  }
+      |  leveldb {
+      |    path = "./leveldb"
+      |  }
+      |}
+      """.stripMargin)
+  ).start()
+
+  // will be useful to use Otoroshi internal apis
+  implicit val env = otoroshi.env
+  implicit val ec = otoroshi.executionContext
+
+  for {
+    _ <- otoroshi.dataStores.serviceGroupDataStore.set(group)
+    _ <- otoroshi.dataStores.apiKeyDataStore.set(apiKey)
+    _ <- otoroshi.dataStores.serviceDescriptorDataStore.set(descriptor)
+  } yield {
+    // here your otoroshi is configured to serve http://127.0.0.1:8080 on http://api.oto.tools:8888
+    // ...
+    otoroshi.stop()
+    System.exit(0)
+  }
+}
+
+
+
+
+
Java
+
+
package foo;
+
+import com.typesafe.config.ConfigFactory;
+import models.ServiceGroup;
+import play.api.Mode;
+import scala.None$;
+import scala.Option;
+import scala.Unit$;
+
+import java.io.File;
+import java.util.Arrays;
+import java.io.File;
+
+public class MyApp {
+
+    public static void main(String... args) {
+        otoroshi.api.Otoroshi oto = otoroshi.api.Otoroshi.apply(
+            play.core.server.ServerConfig.apply(
+                EmbedOto.class.getClassLoader(),
+                new File("."),
+                Option.<Object>apply(8888),
+                Option.<Object>apply(null),
+                "0.0.0.0",
+                Mode.Prod$.MODULE$,
+                System.getProperties()
+            ),
+            ConfigFactory.parseString(String.join("\n", Arrays.asList(
+                "app {",
+                "  storage = \"leveldb\"",
+                "  importFrom = \"./my-state.json\"",
+                "  env = \"prod\"",
+                "  adminapi {",
+                "    targetSubdomain = \"otoroshi-admin-internal-api\"",
+                "    exposedSubdomain = \"otoroshi-api\"",
+                "    defaultValues {",
+                "      backOfficeGroupId = \"admin-api-group\"",
+                "      backOfficeApiKeyClientId = \"admin-api-apikey-id\"",
+                "      backOfficeApiKeyClientSecret = \"admin-api-apikey-secret\"",
+                "      backOfficeServiceId = \"admin-api-service\"",
+                "    }",
+                "  }",
+                "  claim {",
+                "    sharedKey = \"mysecret\"",
+                "  }",
+                "  leveldb {",
+                "    path = \"./leveldb\"",
+                "  }",
+                "}"
+            )))
+        ).start();
+
+        ServiceGroup group = ServiceGroup.apply("id", "name", "description");
+
+        oto.dataStores().serviceGroupDataStore()
+          .set(group, None$.empty(), oto.executionContext(), oto.env())
+          .map(resp -> {
+            // Do whatever you want
+            oto.stop();
+            return Unit$.MODULE$;
+          }, oto.executionContext());
+    }
+}
+
+ +
+
+ +
+
+ +
+
+ +
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + diff --git a/docs/manual/features.html b/docs/manual/features.html new file mode 100644 index 0000000000..9a3c3613c9 --- /dev/null +++ b/docs/manual/features.html @@ -0,0 +1,405 @@ + + + + +Features · Otoroshi + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+ + + +
+
+

Features

+

All the features supported by Otoroshi are listed below

+ + +
+
+
+
+ +
+
+ +
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + diff --git a/docs/manual/firstrun/configfile.html b/docs/manual/firstrun/configfile.html new file mode 100644 index 0000000000..0ef2dcc62f --- /dev/null +++ b/docs/manual/firstrun/configfile.html @@ -0,0 +1,887 @@ + + + + +Config. with files · Otoroshi + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+ + + +
+
+

Config. with files

+

There is a lot of things you can configure in Otoroshi. By default, Otoroshi provides a configuration that should be enough for testing purpose. But you’ll likely need to update this configuration when you’ll need to move into production.

+

In this page, any configuration property can be set at runtime using a -D flag when launching Otoroshi like

+
java -Dhttp.port=8080 -jar otoroshi.jar
+
+

or

+
./bin/otoroshi -Dhttp.port=8080 
+
+

Common configuration

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
name type default value description
app.domain string “oto.tools” the domain on which Otoroshi UI/API is be exposed
app.rootScheme string “http” the scheme on which Otoroshi is exposed, either “http” or “https”
app.snowflake.seed number 0 this number will is used to generate unique ids across the cluster. Each Otorshi instance must have a unique seed.
app.events.maxSize number 1000 max number of analytic and alert events stored locally
app.backoffice.exposed boolean true does the current Otoroshi instance exposed a backoffice ui
app.backoffice.subdomain string “otoroshi” the subdomain on wich Otoroshi backoffice will be served
app.backoffice.session.exp number 86400000 the number of seconds before the Otoroshi backoffice session expires
app.privateapps.subdomain string “privateapps” the subdomain on which private apps UI are served
app.privateapps.session.exp number 86400000 the number of seconds before the private apps session expires
app.claim.sharedKey string “secret” the shared secret used for signing the JWT token passed between Otoroshi and backend services
app.webhooks.size number 100 number of events sent at most when calling one of the analytics webhooks
app.throttlingWindow number 10 time window (in seconds) used to compute throttling quotas for ApiKeys
+

Admin API configuration

+

When Otoroshi starts for the first time, its datastore is empty. As Otoroshi uses Otoroshi to expose its admin REST API, you’ll have to provide the details for the admin API exposition. This part is super important because if you go to production with the default values, your Otoroshi server won’t be secured anymore.

Warning
+

YOU HAVE TO CUSTOMIZE THE FOLLOWING VALUES BEFORE GOING TO PRODUCTION !!

+

Some of the following terms will seem obscure to you, but you will learn their meaning in the following chapters :)

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
name type default value description
app.adminapi.exposed boolean true does the current Otoroshi instance expose an admin API
app.adminapi.targetSubdomain string “otoroshi-admin-internal-api” the subdomain on wich admin API call will be redirected from app.adminapi.exposedSubdomain
app.adminapi.exposedSubdomain string “otoroshi-api” the subdomain on wich the Otoroshi admin API will be exposed
app.adminapi.defaultValues.backOfficeGroupId string “admin-api-group” the name of the service groups that will contain the service descriptors for the Otoroshi admin API
app.adminapi.defaultValues.backOfficeApiKeyClientId string “admin-api-apikey-id” the client id of the Otoroshi admin API apikey
app.adminapi.defaultValues.backOfficeApiKeyClientSecret string “admin-api-apikey-secret” the client secret of the Otoroshi admin API apikey
app.adminapi.defaultValues.backOfficeServiceId string “admin-api-service” the id of the service descriptors for the Otoroshi admin API
app.adminapi.proxy.https boolean false whether or not the current Otoroshi instance serves its content over https. This setting is useful for the backoffice UI to access Otoroshi admin API
app.adminapi.proxy.local boolean true whether or not the admin API is accessible through 127.0.0.1. This setting is useful for the backoffice UI to access Otoroshi admin API
+

DB configuration

+

As Otoroshi supports multiple datastores, you’ll have to provide some details about how to connect/configure it.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
name type default value description
app.storage string “inmemory” what kind of storage engine you want to use. Possible values are inmemory, leveldb, redis, cassandra, mongo
app.importFrom string  a file path or a URL to an Otoroshi export file. If the datastore is empty on startup, this file will be used to import data to the empty DB
app.importFromHeaders array  [] a list of : separated header to use if the app.importFrom setting is a URL
app.initialData object   object representing Otoroshi internal data as exported from danger zone so you don’t need a config file and a data import file
app.redis.host string “localhost” the host of the redis server
app.redis.port number 6379 the port of the redis server
app.redis.slaves array [] the redis slaves lists
app.leveldb.path string “./leveldb” the path where levelDB files will be written
app.cassandra.hosts string “127.0.0.1” the host of the cassandra server
app.cassandra.host string “127.0.0.1” the list of cassandra hosts
app.cassandra.port number 9042 the port of the cassandra servers
app.mongo.uri string mongodb://localhost:27017/defaultthe mongo URI following Mongo semantic https://docs.mongodb.com/manual/reference/connection-string/
+

Headers configuration

+

Otoroshi uses a fair amount of http headers in order to work properly. The name of those headers are customizable to fit your needs.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
name type default value description
otoroshi.headers.trace.label string “Otoroshi-Viz-From-Label” header to pass request tracing informations
otoroshi.headers.trace.from string “Otoroshi-Viz-From” header to pass request tracing informations (ip address)
otoroshi.headers.trace.parent string “Otoroshi-Parent-Request” header to pass request tracing informations (parent request id)
otoroshi.headers.request.adminprofile string “Otoroshi-Admin-Profile” header to pass admin name when the admin API is called from the Otoroshi backoffice
otoroshi.headers.request.clientid string “Otoroshi-Client-Id” header to pass apikey client id
otoroshi.headers.request.clientsecret string “Otoroshi-Client-Secret” header to pass apikey client secret
otoroshi.headers.request.id string “Otoroshi-Request-Id” header containing the id of the current request
otoroshi.headers.response.proxyhost string “Otoroshi-Proxied-Host” header containing the proxied host
otoroshi.headers.response.error string “Otoroshi-Error” header containing whether or not the request generated an error
otoroshi.headers.response.errormsg string “Otoroshi-Error-Msg” header containing error message if some
otoroshi.headers.response.proxylatency string “Otoroshi-Proxy-Latency” header containing the current latency induced by Otoroshi
otoroshi.headers.response.upstreamlatency string “Otoroshi-Upstream-Latency” header containing the current latency from Otoroshi to service backend
otoroshi.headers.response.dailyquota string “Otoroshi-Daily-Calls-Remaining” header containing the number of remaining daily call (apikey)
otoroshi.headers.response.monthlyquota string “Otoroshi-Monthly-Calls-Remaining” header containing the number of remaining monthly call (apikey)
otoroshi.headers.comm.state string “Otoroshi-State” header containing a random value for secured mode
otoroshi.headers.comm.stateresp string “Otoroshi-State-Resp” header containing a random value for secured mode
otoroshi.headers.comm.claim string “Otoroshi-Claim” header containing a JWT token for secured mode
otoroshi.headers.healthcheck.test string “Otoroshi-Health-Check-Logic-Test” header containing a logic test for healthcheck
otoroshi.headers.healthcheck.testresult string “Otoroshi-Health-Check-Logic-Test-Result” header containing the result of a logic test for healthcheck
otoroshi.headers.jwt.issuer string “Otoroshi” the name of the issuer for the JWT token
otoroshi.headers.canary.tracker string “Otoroshi-Canary-Id” header containing the ID of the canary session if enabled
+

Play specific configuration

+

As Otoroshi is a Play app, you should take a look at Play configuration documentation to tune its internal configuration

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
name type default value description
http.port number 8080 the http port used by Otoroshi. You can use ‘disabled’ as value if you don’t want to use http
https.port number disabled the https port used by Otoroshi. You can use ‘disabled’ as value if you don’t want to use https
http2.enabled boolean false whether or not http2 is enabled on the Otoroshi server. You need to configure https (listed bellow) to be able to use it
play.http.secret.key string “secret” the secret used to sign Otoroshi session cookie
play.http.session.secure boolean false whether or not the Otoroshi backoffice session will be served over https only
play.http.session.httpOnly boolean true whether or not the Otoroshi backoffice session will be accessible from Javascript
play.http.session.maxAge number 259200000 the number of seconds before Otoroshi backoffice session expired
play.http.session.domain string “.oto.tools” the domain on which the Otoroshi backoffice session is authorized
play.http.session.cookieName string “otoroshi-session” the name of the Otoroshi backoffice session
play.ws.play.ws.useragent string “Otoroshi” the user agent sent by Otoroshi if not present on the original http request
play.server.https.keyStore.path string the path to the keystore containing the private key and certificate, if not provided generates a keystore for you
play.server.https.keyStore.type string JKS the key store type, defaults to JKS
play.server.https.keyStore.password string ’’ the password, defaults to a blank password
play.server.https.keyStore.algorithm string the key store algorithm, defaults to the platforms default algorithm
+

More config. options

+

See https://github.com/MAIF/otoroshi/blob/master/otoroshi/conf/base.conf and https://github.com/MAIF/otoroshi/blob/master/otoroshi/conf/application.conf

+

if you want to configure https on your Otoroshi server, just read PlayFramework documentation about it

+

Example of configuration file

+
include "application.conf"
+
+http.port = 8080
+
+app {
+  storage = "leveldb"
+  importFrom = "./my-state.json"
+  env = "prod"
+  domain = "oto.tools"
+  rootScheme = "http"
+  snowflake {
+    seed = 0
+  }
+  events {
+    maxSize = 1000
+  }
+  backoffice {
+    subdomain = "otoroshi"
+    session {
+      exp = 86400000
+    }
+  }
+  privateapps {
+    subdomain = "privateapps"
+    session {
+      exp = 86400000
+    }
+  }
+  adminapi {
+    targetSubdomain = "otoroshi-admin-internal-api"
+    exposedSubdomain = "otoroshi-api"
+    defaultValues {
+      backOfficeGroupId = "admin-api-group"
+      backOfficeApiKeyClientId = "admin-api-apikey-id"
+      backOfficeApiKeyClientSecret = "admin-api-apikey-secret"
+      backOfficeServiceId = "admin-api-service"
+    }
+  }
+  claim {
+    sharedKey = "mysecret"
+  }
+  leveldb {
+    path = "./leveldb"
+  }
+}
+
+play.http {
+  session {
+    secure = false
+    httpOnly = true
+    maxAge = 2592000000
+    domain = ".oto.tools"
+    cookieName = "oto-sess"
+  }
+}
+
+ +
+ +
+ +
+
+ +
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + diff --git a/docs/manual/firstrun/datastore.html b/docs/manual/firstrun/datastore.html new file mode 100644 index 0000000000..bf24d0541a --- /dev/null +++ b/docs/manual/firstrun/datastore.html @@ -0,0 +1,338 @@ + + + + +Choose your datastore · Otoroshi + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+ + + +
+
+

Choose your datastore

+

Right now, Otoroshi supports multiple datastore.

+

You can choose one datastore over another depending on your use case.

+

Available datastores are the following :

+
    +
  • in memory
  • +
  • redis
  • +
  • cassandra (experimental support)
  • +
  • mongodb (experimental support)
  • +
  • levelDB (not suitable for production usage)
  • +
+

The levelDB datastore is pretty handy for testing purposes, but is not supposed to be used in production mode.

+

The in-memory datastore is kind of interesting… It can be used for testing purposes, but it is also a good candidate for production because of its fastness. But in that case, you need to provide a way to feed the deployed in-memory instances after the initial boot. In a future release (i.e. not yet :D), we will provide a master/workers slave based on Otoroshi in memory instances and kafka (see https://github.com/MAIF/otoroshi/issues/8 for more details about the feature).

+

The redis datastore is quite nice when you want to easily deploy several Otoroshi instances.

+

If you need a datastore more scalable than redis, then you can use the cassandra datastore or the mongodb datastore.

+

We plan to add more datastores support in the future :)

+
+ +
+
+
+
+ +
+
+ +
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + diff --git a/docs/manual/firstrun/env.html b/docs/manual/firstrun/env.html new file mode 100644 index 0000000000..5d6cd519c2 --- /dev/null +++ b/docs/manual/firstrun/env.html @@ -0,0 +1,374 @@ + + + + +Config. with ENVs · Otoroshi + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+ + + +
+
+

Config. with ENVs

+

Now that you know how to configure Otoroshi with the config. file every property in the following block can be overriden by an environment variable (an env. variable is written like ${?ENV_VARIABLE}).

+
app.storage = ${?APP_STORAGE}
+app.importFrom = ${?APP_IMPORT_FROM}
+app.domain = ${?APP_DOMAIN}
+app.rootScheme = ${?APP_ROOT_SCHEME}
+app.throttlingWindow = ${?THROTTLING_WINDOW}
+app.snowflake.seed = ${?INSTANCE_NUMBER}
+app.events.maxSize = ${?MAX_EVENTS_SIZE}
+app.backoffice.exposed = ${?APP_BACKOFFICE_EXPOSED}
+app.backoffice.subdomain = ${?APP_BACKOFFICE_SUBDOMAIN}
+app.backoffice.session.exp = ${?APP_BACKOFFICE_SESSION_EXP}
+app.privateapps.subdomain = ${?APP_PRIVATEAPPS_SUBDOMAIN}
+app.privateapps.session.exp = ${?APP_PRIVATEAPPS_SESSION_EXP}
+app.adminapi.exposed = ${?ADMIN_API_EXPOSED}
+app.adminapi.targetSubdomain = ${?ADMIN_API_TARGET_SUBDOMAIN}
+app.adminapi.exposedSubdomain = ${?ADMIN_API_EXPOSED_SUBDOMAIN}
+app.adminapi.defaultValues.backOfficeGroupId = ${?ADMIN_API_GROUP}
+app.adminapi.defaultValues.backOfficeApiKeyClientId = ${?ADMIN_API_CLIENT_ID}
+app.adminapi.defaultValues.backOfficeApiKeyClientSecret = ${?ADMIN_API_CLIENT_SECRET}
+app.adminapi.defaultValues.backOfficeServiceId = ${?ADMIN_API_SERVICE_ID}
+app.adminapi.proxy.https = ${?ADMIN_API_HTTPS}
+app.adminapi.proxy.local = ${?ADMIN_API_LOCAL}
+app.claim.sharedKey = ${?CLAIM_SHAREDKEY}
+app.webhooks.size = ${?WEBHOOK_SIZE}
+app.redis.host = ${?REDIS_HOST}
+app.redis.port = ${?REDIS_PORT}
+app.redis.password = ${?REDIS_PASSWORD}
+app.redis.windowSize = ${?REDIS_WINDOW_SIZE}
+app.redis.useScan =  ${?REDIS_USE_SCAN}
+app.inmemory.windowSize = ${?INMEMORY_WINDOW_SIZE}
+app.leveldb.windowSize = ${?LEVELDB_WINDOW_SIZE}
+app.leveldb.path = ${?LEVELDB_PATH}
+app.cassandra.windowSize = ${?CASSANDRA_WINDOW_SIZE}
+app.cassandra.hosts = ${?CASSANDRA_HOSTS}
+app.cassandra.host = ${?CASSANDRA_HOST}
+app.cassandra.port = ${?CASSANDRA_PORT}
+app.elastic.url = ${?ELASTIC_URL}
+app.elastic.user = ${?ELASTIC_USER}
+app.elastic.password = ${?ELASTIC_PASSWORD}
+app.elastic.index = ${?ELASTIC_INDEX}
+app.elastic.type = ${?ELASTIC_TYPE}
+app.mongo.uri = ${?MONGO_URI}
+http.port = ${?PORT}
+https.port = ${?HTTPS_PORT}
+http2.enabled = ${?HTTP2_ENABLED}
+play.http.secret.key = ${?PLAY_CRYPTO_SECRET}
+play.http.session.secure = ${?SESSION_SECURE_ONLY}
+play.http.session.maxAge = ${?SESSION_MAX_AGE}
+play.http.session.domain = ${?SESSION_DOMAIN}
+play.http.session.cookieName = ${?SESSION_NAME}
+play.ws.play.ws.useragent=${?USER_AGENT}
+
+ +
+
+
+
+ +
+
+ +
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + diff --git a/docs/manual/firstrun/host.html b/docs/manual/firstrun/host.html new file mode 100644 index 0000000000..c2f59ba17f --- /dev/null +++ b/docs/manual/firstrun/host.html @@ -0,0 +1,334 @@ + + + + +Setup your hosts · Otoroshi + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+ + + +
+
+

Setup your hosts

+

By default, Otoroshi starts with domain oto.tools that targets 127.0.0.1. Of course you can change the domain, you have to add the values in your /etc/hosts file according to the setting you put in Otoroshi configuration

+
    +
  • app.domain => oto.tools
  • +
  • app.backoffice.subdomain => otoroshi
  • +
  • app.privateapps.subdomain => privateapps
  • +
  • app.adminapi.exposedSubdomain => otoroshi-api
  • +
  • app.adminapi.targetSubdomain => otoroshi-admin-internal-api
  • +
+

for instance if you want to change the default domain and use something like otoroshi.mydomain.org, then start otoroshi like

+
java -Dapp.domain=mydomain.org -jar otoroshi.jar
+
Warning
+

Otoroshi cannot be accessed using http://127.0.0.1:8080 or http://localhost:8080 because Otoroshi uses Otoroshi to serve it’s own UI and API. When otoroshi starts with an empty database, it will create a service descriptor for that using app.domain and the settings listed on this page and in the * Config. with files page that serve Otoroshi API and UI on http://otoroshi-api.${app.domain} and http://otoroshi.${app.domain}. Once the descriptor is saved in database, if you want to change app.domain, you’ll have to edit the descriptor in the database or restart Otoroshi with an empty database.

+ +
+
+
+
+ +
+
+ +
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + diff --git a/docs/manual/firstrun/index.html b/docs/manual/firstrun/index.html new file mode 100644 index 0000000000..80ad7e22ac --- /dev/null +++ b/docs/manual/firstrun/index.html @@ -0,0 +1,324 @@ + + + + +First run · Otoroshi + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+ + + +
+
+

First run

+

Now that you have your own distro of Otoroshi, it’s time to run it.

+

But before doing so, you’ll have to make some choices about some essential stuff in order to have your own customized version of Otoroshi.

+

Let’s start with the datastore

+ +
+
+
+
+ +
+
+ +
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + diff --git a/docs/manual/firstrun/initialstate.html b/docs/manual/firstrun/initialstate.html new file mode 100644 index 0000000000..bd8d1c118f --- /dev/null +++ b/docs/manual/firstrun/initialstate.html @@ -0,0 +1,438 @@ + + + + +Import initial state · Otoroshi + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+ + + +
+
+

Import initial state

+

Now you are almost ready to run Otoroshi for the first time, but maybe you want to import data from previous Otoroshi installation in your current datastore.

+

To do that, you need to add the app.importFrom setting to the Otoroshi configuration (of $APP_IMPORT_FROM env).

+

It can be a file path or a URL

+

Example of export

+
{
+  "config": {
+    "lines": ["prod"],    
+    "limitConcurrentRequests": true,
+    "maxConcurrentRequests": 500,
+    "useCircuitBreakers": true,
+    "apiReadOnly": false,
+    "registerFromCleverHook": false,
+    "u2fLoginOnly": true,
+    "ipFiltering": {
+      "whitelist": [],
+      "blacklist": []
+    },
+    "throttlingQuota": 100000,
+    "perIpThrottlingQuota": 500,
+    "analyticsEventsUrl": null,
+    "analyticsWebhooks": [],
+    "alertsWebhooks": [],
+    "alertsEmails": [],
+    "endlessIpAddresses": []
+  },
+  "admins": [],
+  "simpleAdmins": [
+    {
+      "username": "admin@otoroshi.io",
+      "password": "xxxxxxxxxxxxxxxxx",
+      "label": "Otoroshi Admin",
+      "createdAt": 1493971715708
+    }
+  ],
+  "serviceGroups": [
+    {
+      "id": "default",
+      "name": "default-group",
+      "description": "The default group"
+    },
+    {
+      "id": "admin-api-group",
+      "name": "Otoroshi Admin Api group",
+      "description": "No description"
+    }
+  ],
+  "apiKeys": [
+    {
+      "clientId": "admin-api-apikey-id",
+      "clientSecret": "admin-api-apikey-secret",
+      "clientName": "Otoroshi Backoffice ApiKey",
+      "authorizedGroup": "admin-api-group",
+      "enabled": true,
+      "throttlingQuota": 10000000,
+      "dailyQuota": 10000000,
+      "monthlyQuota": 10000000,
+      "metadata": {}
+    }
+  ],
+  "serviceDescriptors": [
+    {
+      "id": "admin-api-service",
+      "groupId": "admin-api-group",
+      "name": "otoroshi-admin-api",
+      "env": "prod",
+      "domain": "oto.tools",
+      "subdomain": "otoroshi-api",
+      "targets": [
+        {
+          "host": "localhost:8080",
+          "scheme": "http"
+        }
+      ],
+      "root": "/",
+      "enabled": true,
+      "privateApp": false,
+      "forceHttps": false,
+      "maintenanceMode": false,
+      "buildMode": false,
+      "enforceSecureCommunication": true,
+      "publicPatterns": [],
+      "privatePatterns": [],
+      "additionalHeaders": {
+        "Host": "otoroshi-admin-internal-api.oto.tools"
+      },
+      "matchingHeaders": {},
+      "ipFiltering": {
+        "whitelist": [],
+        "blacklist": []
+      },
+      "api": {
+        "exposeApi": false
+      },
+      "healthCheck": {
+        "enabled": false,
+        "url": "/"
+      },
+      "metadata": {}
+    }
+  ],
+  "errorTemplates": []
+}
+
+ +
+
+ +
+
+ +
+
+ +
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + diff --git a/docs/manual/firstrun/run.html b/docs/manual/firstrun/run.html new file mode 100644 index 0000000000..45c25be7e8 --- /dev/null +++ b/docs/manual/firstrun/run.html @@ -0,0 +1,394 @@ + + + + +Run Otoroshi · Otoroshi + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+ + + +
+
+

Run Otoroshi

+

Now you are ready to run Otoroshi. You can run the following command with some tweaks depending on the way you want to configure Otoroshi. If you want to pass a custom configuration file, use the -Dconfig.file=/path/to/file.conf flag in the following commands.

+

From .zip file

+
unzip otoroshi-dist.zip
+cd otoroshi-vx.x.x
+./bin/otoroshi
+
+

From .jar file

+

For Java 8 & Java 11

+
java -jar otoroshi.jar
+
+

From docker

+
docker run -p "8080:8080" maif/otoroshi:1.4.8-dev
+
+

You can also pass useful args like :

+
docker run -p "8080:8080" otoroshi -Dconfig.file=/usr/app/otoroshi/conf/otoroshi.conf -Dlogger.file=/usr/app/otoroshi/conf/otoroshi.xml
+
+

If you want to provide your own config file, you can read the documentation about config files.

+

You can also provide some ENV variable using the --env flag to customize your Otoroshi instance.

+

The list of possible env variables is available here.

+

You can use a volume to provide configuration like :

+
docker run -p "8080:8080" -v "$(pwd):/usr/app/otoroshi/conf" maif/otoroshi
+
+

You can also use a volume if you choose to use leveldb datastore like :

+
docker run -p "8080:8080" -v "$(pwd)/leveldb:/usr/app/otoroshi/leveldb" maif/otoroshi -Dapp.storage=leveldb
+
+

You can also use a volume if you choose to use exports files :

+
docker run -p "8080:8080" -v "$(pwd):/usr/app/otoroshi/imports" maif/otoroshi -Dapp.importFrom=/usr/app/otoroshi/imports/export.json
+
+

Run examples

+
$ java \
+  -Xms2G \
+  -Xmx8G \
+  -Dhttp.port=8080 \
+  -Dapp.importFrom=/home/user/otoroshi.json \
+  -Dconfig.file=/home/user/otoroshi.conf \
+  -jar ./otoroshi.jar
+
+[warn] otoroshi-in-memory-datastores - Now using InMemory DataStores
+[warn] otoroshi-env - The main datastore seems to be empty, registering some basic services
+[warn] otoroshi-env - Importing from: /home/user/otoroshi.json
+[info] play.api.Play - Application started (Prod)
+[info] p.c.s.AkkaHttpServer - Listening for HTTP on /0:0:0:0:0:0:0:0:8080
+
+

If you choose to start Otoroshi without importing existing data, Otoroshi will create a new admin user and print the login details in the log. When you will log into the admin dashboard, Otoroshi will ask you to create another account to avoid security issues.

+
$ java \
+  -Xms2G \
+  -Xmx8G \
+  -Dhttp.port=8080 \
+  -jar otoroshi.jar
+
+[warn] otoroshi-in-memory-datastores - Now using InMemory DataStores
+[warn] otoroshi-env - The main datastore seems to be empty, registering some basic services
+[warn] otoroshi-env - You can log into the Otoroshi admin console with the following credentials: admin@otoroshi.io / HHUsiF2UC3OPdmg0lGngEv3RrbIwWV5W
+[info] play.api.Play - Application started (Prod)
+[info] p.c.s.AkkaHttpServer - Listening for HTTP on /0:0:0:0:0:0:0:0:8080
+
+ +
+
+ +
+
+ +
+
+ +
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + diff --git a/docs/manual/getotoroshi/frombinaries.html b/docs/manual/getotoroshi/frombinaries.html new file mode 100644 index 0000000000..6777f0a6c1 --- /dev/null +++ b/docs/manual/getotoroshi/frombinaries.html @@ -0,0 +1,324 @@ + + + + +From binaries · Otoroshi + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+ + + +
+
+

From binaries

+

If you want to download the last version of Otoroshi and its CLI, you can grab them from the release page of the Otoroshi github page :

+

Go to https://github.com/MAIF/otoroshi/releases and get the last version of the otoroshi-dist.zip file or otoroshi.jar file

+ +
+
+
+
+ +
+
+ +
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + diff --git a/docs/manual/getotoroshi/fromdocker.html b/docs/manual/getotoroshi/fromdocker.html new file mode 100644 index 0000000000..edab003ebb --- /dev/null +++ b/docs/manual/getotoroshi/fromdocker.html @@ -0,0 +1,338 @@ + + + + +From docker · Otoroshi + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+ + + +
+
+

From docker

+

If you’re a Docker aficionado, Otoroshi is provided as a Docker image that your can pull directly from Official repos.

+

first, fetch the last Docker image of Otoroshi :

+
docker pull maif/otoroshi:1.4.18
+# or 
+docker pull maif/otoroshi:latest
+# or 
+docker pull maif/otoroshi:jdk8-1.4.18
+# or 
+docker pull maif/otoroshi:jdk11-1.4.18
+# or 
+docker pull maif/otoroshi:jdk12-1.4.18
+# or 
+docker pull maif/otoroshi:jdk13-1.4.18
+# or 
+docker pull maif/otoroshi:jdk14-1.4.18
+
+ +
+
+
+
+ +
+
+ +
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + diff --git a/docs/manual/getotoroshi/fromsources.html b/docs/manual/getotoroshi/fromsources.html new file mode 100644 index 0000000000..a2efac4db4 --- /dev/null +++ b/docs/manual/getotoroshi/fromsources.html @@ -0,0 +1,369 @@ + + + + +From sources · Otoroshi + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+ + + +
+
+

From sources

+

to build Otoroshi from sources, you need the following tools :

+
    +
  • git
  • +
  • JDK 8
  • +
  • SBT
  • +
  • node
  • +
  • yarn
  • +
+

Once you’ve installed all those tools, go to the Otoroshi github page and clone the sources :

+
git clone https://github.com/MAIF/otoroshi.git --depth=1
+
+

then you need to run the build.sh script to build the documentation, the React UI and the server :

+
sh ./scripts/build.sh
+
+

and that’s all, you can grab your Otoroshi package at otoroshi/target/scala-2.12/otoroshi or otoroshi/target/universal/.

+

For those who want to build only parts of Otoroshi, read the following.

+

Build the documentation only

+

Go to the documentation folder and run :

+
sbt ';clean;paradox'
+
+

The documentation is located at documentation/target/paradox/site/main/

+

Build the React UI

+

Go to the otoroshi/javascript folder and run :

+
yarn install
+yarn build
+
+

You will find the JS bundle at otoroshi/public/javascripts/bundle/bundle.js.

+

Build the Otoroshi server

+

Go to the otoroshi folder and run :

+
sbt ';clean;compile;dist;assembly'
+
+

You will find your Otoroshi package at otoroshi/target/scala-2.12/otoroshi or otoroshi/target/universal/.

+ +
+ +
+ +
+
+ +
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + diff --git a/docs/manual/getotoroshi/index.html b/docs/manual/getotoroshi/index.html new file mode 100644 index 0000000000..40d063a071 --- /dev/null +++ b/docs/manual/getotoroshi/index.html @@ -0,0 +1,323 @@ + + + + +Get Otoroshi · Otoroshi + + + + + + + + + + + + + + + +
+
+ + + +
+ + + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + diff --git a/docs/manual/img/add-apikey.png b/docs/manual/img/add-apikey.png new file mode 100644 index 0000000000..657427c4db Binary files /dev/null and b/docs/manual/img/add-apikey.png differ diff --git a/docs/manual/img/admin-created-admin.png b/docs/manual/img/admin-created-admin.png new file mode 100644 index 0000000000..872d62bb7a Binary files /dev/null and b/docs/manual/img/admin-created-admin.png differ diff --git a/docs/manual/img/admin-sessions.png b/docs/manual/img/admin-sessions.png new file mode 100644 index 0000000000..d6cf37bd3f Binary files /dev/null and b/docs/manual/img/admin-sessions.png differ diff --git a/docs/manual/img/admin-users-sessions.png b/docs/manual/img/admin-users-sessions.png new file mode 100644 index 0000000000..ab5541f2b3 Binary files /dev/null and b/docs/manual/img/admin-users-sessions.png differ diff --git a/docs/manual/img/alerts-log.png b/docs/manual/img/alerts-log.png new file mode 100644 index 0000000000..553e27bdab Binary files /dev/null and b/docs/manual/img/alerts-log.png differ diff --git a/docs/manual/img/all-services.png b/docs/manual/img/all-services.png new file mode 100644 index 0000000000..43a200693a Binary files /dev/null and b/docs/manual/img/all-services.png differ diff --git a/docs/manual/img/apikey-delete-confirm.png b/docs/manual/img/apikey-delete-confirm.png new file mode 100644 index 0000000000..b805b3fa0f Binary files /dev/null and b/docs/manual/img/apikey-delete-confirm.png differ diff --git a/docs/manual/img/apikey-delete.png b/docs/manual/img/apikey-delete.png new file mode 100644 index 0000000000..837506d514 Binary files /dev/null and b/docs/manual/img/apikey-delete.png differ diff --git a/docs/manual/img/apikey-edit.png b/docs/manual/img/apikey-edit.png new file mode 100644 index 0000000000..62f2c5ebcd Binary files /dev/null and b/docs/manual/img/apikey-edit.png differ diff --git a/docs/manual/img/apikey-update.png b/docs/manual/img/apikey-update.png new file mode 100644 index 0000000000..02de5535e4 Binary files /dev/null and b/docs/manual/img/apikey-update.png differ diff --git a/docs/manual/img/apikey.png b/docs/manual/img/apikey.png new file mode 100644 index 0000000000..785a257869 Binary files /dev/null and b/docs/manual/img/apikey.png differ diff --git a/docs/manual/img/apikeys-list.png b/docs/manual/img/apikeys-list.png new file mode 100644 index 0000000000..d5a3b1fe9b Binary files /dev/null and b/docs/manual/img/apikeys-list.png differ diff --git a/docs/manual/img/architecture-1-bis.png b/docs/manual/img/architecture-1-bis.png new file mode 100644 index 0000000000..0ba9559d8d Binary files /dev/null and b/docs/manual/img/architecture-1-bis.png differ diff --git a/docs/manual/img/architecture-1.png b/docs/manual/img/architecture-1.png new file mode 100644 index 0000000000..c522be558f Binary files /dev/null and b/docs/manual/img/architecture-1.png differ diff --git a/docs/manual/img/architecture-2-bis.png b/docs/manual/img/architecture-2-bis.png new file mode 100644 index 0000000000..768b63cd4c Binary files /dev/null and b/docs/manual/img/architecture-2-bis.png differ diff --git a/docs/manual/img/architecture-2.png b/docs/manual/img/architecture-2.png new file mode 100644 index 0000000000..a5900036b9 Binary files /dev/null and b/docs/manual/img/architecture-2.png differ diff --git a/docs/manual/img/audit-log.png b/docs/manual/img/audit-log.png new file mode 100644 index 0000000000..01f0364d56 Binary files /dev/null and b/docs/manual/img/audit-log.png differ diff --git a/docs/manual/img/auth0-settings.png b/docs/manual/img/auth0-settings.png new file mode 100644 index 0000000000..3a262be6de Binary files /dev/null and b/docs/manual/img/auth0-settings.png differ diff --git a/docs/manual/img/base-page.png b/docs/manual/img/base-page.png new file mode 100644 index 0000000000..23a5afbf33 Binary files /dev/null and b/docs/manual/img/base-page.png differ diff --git a/docs/manual/img/clevercloud-integration-1.png b/docs/manual/img/clevercloud-integration-1.png new file mode 100644 index 0000000000..c89d4c5196 Binary files /dev/null and b/docs/manual/img/clevercloud-integration-1.png differ diff --git a/docs/manual/img/clevercloud-integration-2.png b/docs/manual/img/clevercloud-integration-2.png new file mode 100644 index 0000000000..b28292f1c3 Binary files /dev/null and b/docs/manual/img/clevercloud-integration-2.png differ diff --git a/docs/manual/img/clevercloud-integration-3.png b/docs/manual/img/clevercloud-integration-3.png new file mode 100644 index 0000000000..53a7084df2 Binary files /dev/null and b/docs/manual/img/clevercloud-integration-3.png differ diff --git a/docs/manual/img/cluster-1.png b/docs/manual/img/cluster-1.png new file mode 100644 index 0000000000..ecb32065ea Binary files /dev/null and b/docs/manual/img/cluster-1.png differ diff --git a/docs/manual/img/cluster-2.png b/docs/manual/img/cluster-2.png new file mode 100644 index 0000000000..dc84eefec2 Binary files /dev/null and b/docs/manual/img/cluster-2.png differ diff --git a/docs/manual/img/cluster-3.png b/docs/manual/img/cluster-3.png new file mode 100644 index 0000000000..e7e35c744b Binary files /dev/null and b/docs/manual/img/cluster-3.png differ diff --git a/docs/manual/img/cluster-4.png b/docs/manual/img/cluster-4.png new file mode 100644 index 0000000000..0f9dbfa027 Binary files /dev/null and b/docs/manual/img/cluster-4.png differ diff --git a/docs/manual/img/cluster-5.jpg b/docs/manual/img/cluster-5.jpg new file mode 100644 index 0000000000..e301061e03 Binary files /dev/null and b/docs/manual/img/cluster-5.jpg differ diff --git a/docs/manual/img/cluster-6.png b/docs/manual/img/cluster-6.png new file mode 100644 index 0000000000..455da288c4 Binary files /dev/null and b/docs/manual/img/cluster-6.png differ diff --git a/docs/manual/img/cors.png b/docs/manual/img/cors.png new file mode 100644 index 0000000000..f08c4c17a9 Binary files /dev/null and b/docs/manual/img/cors.png differ diff --git a/docs/manual/img/create-admin.png b/docs/manual/img/create-admin.png new file mode 100644 index 0000000000..1aee4e334e Binary files /dev/null and b/docs/manual/img/create-admin.png differ diff --git a/docs/manual/img/create-apikey.png b/docs/manual/img/create-apikey.png new file mode 100644 index 0000000000..fae847f371 Binary files /dev/null and b/docs/manual/img/create-apikey.png differ diff --git a/docs/manual/img/danger-zone-1-commons.png b/docs/manual/img/danger-zone-1-commons.png new file mode 100644 index 0000000000..5e1646a6bc Binary files /dev/null and b/docs/manual/img/danger-zone-1-commons.png differ diff --git a/docs/manual/img/danger-zone-10-clevercloud.png b/docs/manual/img/danger-zone-10-clevercloud.png new file mode 100644 index 0000000000..037ad74fce Binary files /dev/null and b/docs/manual/img/danger-zone-10-clevercloud.png differ diff --git a/docs/manual/img/danger-zone-11-bottom.png b/docs/manual/img/danger-zone-11-bottom.png new file mode 100644 index 0000000000..53b592cafc Binary files /dev/null and b/docs/manual/img/danger-zone-11-bottom.png differ diff --git a/docs/manual/img/danger-zone-2-whitelist-blacklist.png b/docs/manual/img/danger-zone-2-whitelist-blacklist.png new file mode 100644 index 0000000000..83a3b91378 Binary files /dev/null and b/docs/manual/img/danger-zone-2-whitelist-blacklist.png differ diff --git a/docs/manual/img/danger-zone-3-throttling.png b/docs/manual/img/danger-zone-3-throttling.png new file mode 100644 index 0000000000..58a4957975 Binary files /dev/null and b/docs/manual/img/danger-zone-3-throttling.png differ diff --git a/docs/manual/img/danger-zone-4-analytics.png b/docs/manual/img/danger-zone-4-analytics.png new file mode 100644 index 0000000000..b6504e3a2d Binary files /dev/null and b/docs/manual/img/danger-zone-4-analytics.png differ diff --git a/docs/manual/img/danger-zone-5-kafka.png b/docs/manual/img/danger-zone-5-kafka.png new file mode 100644 index 0000000000..3900436bf2 Binary files /dev/null and b/docs/manual/img/danger-zone-5-kafka.png differ diff --git a/docs/manual/img/danger-zone-6-alerts.png b/docs/manual/img/danger-zone-6-alerts.png new file mode 100644 index 0000000000..78737d31e3 Binary files /dev/null and b/docs/manual/img/danger-zone-6-alerts.png differ diff --git a/docs/manual/img/danger-zone-7-statsd.png b/docs/manual/img/danger-zone-7-statsd.png new file mode 100644 index 0000000000..df5cc3075d Binary files /dev/null and b/docs/manual/img/danger-zone-7-statsd.png differ diff --git a/docs/manual/img/danger-zone-8-auth0.png b/docs/manual/img/danger-zone-8-auth0.png new file mode 100644 index 0000000000..d3e05ab449 Binary files /dev/null and b/docs/manual/img/danger-zone-8-auth0.png differ diff --git a/docs/manual/img/danger-zone-9-mailgun.png b/docs/manual/img/danger-zone-9-mailgun.png new file mode 100644 index 0000000000..312ac5f956 Binary files /dev/null and b/docs/manual/img/danger-zone-9-mailgun.png differ diff --git a/docs/manual/img/datastores.png b/docs/manual/img/datastores.png new file mode 100644 index 0000000000..0fb705fd39 Binary files /dev/null and b/docs/manual/img/datastores.png differ diff --git a/docs/manual/img/delete.png b/docs/manual/img/delete.png new file mode 100644 index 0000000000..dc594d8e0d Binary files /dev/null and b/docs/manual/img/delete.png differ diff --git a/docs/manual/img/deploy-cc-0.png b/docs/manual/img/deploy-cc-0.png new file mode 100644 index 0000000000..a965d50ea1 Binary files /dev/null and b/docs/manual/img/deploy-cc-0.png differ diff --git a/docs/manual/img/deploy-cc-1.png b/docs/manual/img/deploy-cc-1.png new file mode 100644 index 0000000000..2aa8bb705c Binary files /dev/null and b/docs/manual/img/deploy-cc-1.png differ diff --git a/docs/manual/img/deploy-cc-2.png b/docs/manual/img/deploy-cc-2.png new file mode 100644 index 0000000000..0ae38a2cde Binary files /dev/null and b/docs/manual/img/deploy-cc-2.png differ diff --git a/docs/manual/img/deploy-cc-3.png b/docs/manual/img/deploy-cc-3.png new file mode 100644 index 0000000000..fefc22354c Binary files /dev/null and b/docs/manual/img/deploy-cc-3.png differ diff --git a/docs/manual/img/deploy-cc-4-bis.png b/docs/manual/img/deploy-cc-4-bis.png new file mode 100644 index 0000000000..fbe3fb7427 Binary files /dev/null and b/docs/manual/img/deploy-cc-4-bis.png differ diff --git a/docs/manual/img/deploy-cc-4.png b/docs/manual/img/deploy-cc-4.png new file mode 100644 index 0000000000..bdccc4ec4b Binary files /dev/null and b/docs/manual/img/deploy-cc-4.png differ diff --git a/docs/manual/img/deploy-cc-5.png b/docs/manual/img/deploy-cc-5.png new file mode 100644 index 0000000000..cc3b2a9b58 Binary files /dev/null and b/docs/manual/img/deploy-cc-5.png differ diff --git a/docs/manual/img/deploy-cc-jar-0.png b/docs/manual/img/deploy-cc-jar-0.png new file mode 100644 index 0000000000..3bb53e2a2c Binary files /dev/null and b/docs/manual/img/deploy-cc-jar-0.png differ diff --git a/docs/manual/img/deploy-cc-jar-1.png b/docs/manual/img/deploy-cc-jar-1.png new file mode 100644 index 0000000000..e244081a8e Binary files /dev/null and b/docs/manual/img/deploy-cc-jar-1.png differ diff --git a/docs/manual/img/deploy-elb-0.png b/docs/manual/img/deploy-elb-0.png new file mode 100644 index 0000000000..47f176a8a2 Binary files /dev/null and b/docs/manual/img/deploy-elb-0.png differ diff --git a/docs/manual/img/deploy-elb-1.png b/docs/manual/img/deploy-elb-1.png new file mode 100644 index 0000000000..e52c64eb5f Binary files /dev/null and b/docs/manual/img/deploy-elb-1.png differ diff --git a/docs/manual/img/deploy-elb-10.png b/docs/manual/img/deploy-elb-10.png new file mode 100644 index 0000000000..614dfa9d59 Binary files /dev/null and b/docs/manual/img/deploy-elb-10.png differ diff --git a/docs/manual/img/deploy-elb-11.png b/docs/manual/img/deploy-elb-11.png new file mode 100644 index 0000000000..9b3fc8f378 Binary files /dev/null and b/docs/manual/img/deploy-elb-11.png differ diff --git a/docs/manual/img/deploy-elb-12.png b/docs/manual/img/deploy-elb-12.png new file mode 100644 index 0000000000..cef06c52f9 Binary files /dev/null and b/docs/manual/img/deploy-elb-12.png differ diff --git a/docs/manual/img/deploy-elb-13.png b/docs/manual/img/deploy-elb-13.png new file mode 100644 index 0000000000..78c531aee4 Binary files /dev/null and b/docs/manual/img/deploy-elb-13.png differ diff --git a/docs/manual/img/deploy-elb-14.png b/docs/manual/img/deploy-elb-14.png new file mode 100644 index 0000000000..69f4db325d Binary files /dev/null and b/docs/manual/img/deploy-elb-14.png differ diff --git a/docs/manual/img/deploy-elb-15.png b/docs/manual/img/deploy-elb-15.png new file mode 100644 index 0000000000..daca945f0d Binary files /dev/null and b/docs/manual/img/deploy-elb-15.png differ diff --git a/docs/manual/img/deploy-elb-16.png b/docs/manual/img/deploy-elb-16.png new file mode 100644 index 0000000000..78846f84c3 Binary files /dev/null and b/docs/manual/img/deploy-elb-16.png differ diff --git a/docs/manual/img/deploy-elb-17.png b/docs/manual/img/deploy-elb-17.png new file mode 100644 index 0000000000..a0c85b30cf Binary files /dev/null and b/docs/manual/img/deploy-elb-17.png differ diff --git a/docs/manual/img/deploy-elb-18.png b/docs/manual/img/deploy-elb-18.png new file mode 100644 index 0000000000..2494ff350c Binary files /dev/null and b/docs/manual/img/deploy-elb-18.png differ diff --git a/docs/manual/img/deploy-elb-2.png b/docs/manual/img/deploy-elb-2.png new file mode 100644 index 0000000000..157ac3849a Binary files /dev/null and b/docs/manual/img/deploy-elb-2.png differ diff --git a/docs/manual/img/deploy-elb-3.png b/docs/manual/img/deploy-elb-3.png new file mode 100644 index 0000000000..c1daab6c3b Binary files /dev/null and b/docs/manual/img/deploy-elb-3.png differ diff --git a/docs/manual/img/deploy-elb-4.png b/docs/manual/img/deploy-elb-4.png new file mode 100644 index 0000000000..0af025fa7a Binary files /dev/null and b/docs/manual/img/deploy-elb-4.png differ diff --git a/docs/manual/img/deploy-elb-5.png b/docs/manual/img/deploy-elb-5.png new file mode 100644 index 0000000000..96814636ec Binary files /dev/null and b/docs/manual/img/deploy-elb-5.png differ diff --git a/docs/manual/img/deploy-elb-6.png b/docs/manual/img/deploy-elb-6.png new file mode 100644 index 0000000000..2d27be8127 Binary files /dev/null and b/docs/manual/img/deploy-elb-6.png differ diff --git a/docs/manual/img/deploy-elb-7.png b/docs/manual/img/deploy-elb-7.png new file mode 100644 index 0000000000..60c3e63eb4 Binary files /dev/null and b/docs/manual/img/deploy-elb-7.png differ diff --git a/docs/manual/img/deploy-elb-8.png b/docs/manual/img/deploy-elb-8.png new file mode 100644 index 0000000000..a59130cd7a Binary files /dev/null and b/docs/manual/img/deploy-elb-8.png differ diff --git a/docs/manual/img/deploy-elb-9.png b/docs/manual/img/deploy-elb-9.png new file mode 100644 index 0000000000..9ff31e9409 Binary files /dev/null and b/docs/manual/img/deploy-elb-9.png differ diff --git a/docs/manual/img/discard-admin-user.png b/docs/manual/img/discard-admin-user.png new file mode 100644 index 0000000000..d91ad44795 Binary files /dev/null and b/docs/manual/img/discard-admin-user.png differ diff --git a/docs/manual/img/edit.png b/docs/manual/img/edit.png new file mode 100644 index 0000000000..ad8bac05d0 Binary files /dev/null and b/docs/manual/img/edit.png differ diff --git a/docs/manual/img/exchange.png b/docs/manual/img/exchange.png new file mode 100644 index 0000000000..09a7599948 Binary files /dev/null and b/docs/manual/img/exchange.png differ diff --git a/docs/manual/img/first-admins-screen.png b/docs/manual/img/first-admins-screen.png new file mode 100644 index 0000000000..990b6ac130 Binary files /dev/null and b/docs/manual/img/first-admins-screen.png differ diff --git a/docs/manual/img/first-login.gif b/docs/manual/img/first-login.gif new file mode 100644 index 0000000000..b5f5c34045 Binary files /dev/null and b/docs/manual/img/first-login.gif differ diff --git a/docs/manual/img/first-login.png b/docs/manual/img/first-login.png new file mode 100644 index 0000000000..8aee2a3ffd Binary files /dev/null and b/docs/manual/img/first-login.png differ diff --git a/docs/manual/img/full-export-1.png b/docs/manual/img/full-export-1.png new file mode 100644 index 0000000000..656107e08f Binary files /dev/null and b/docs/manual/img/full-export-1.png differ diff --git a/docs/manual/img/full-export-2.png b/docs/manual/img/full-export-2.png new file mode 100644 index 0000000000..becc999e15 Binary files /dev/null and b/docs/manual/img/full-export-2.png differ diff --git a/docs/manual/img/full-import-1-bis.png b/docs/manual/img/full-import-1-bis.png new file mode 100644 index 0000000000..0dfc714236 Binary files /dev/null and b/docs/manual/img/full-import-1-bis.png differ diff --git a/docs/manual/img/full-import-1.png b/docs/manual/img/full-import-1.png new file mode 100644 index 0000000000..667ffe54da Binary files /dev/null and b/docs/manual/img/full-import-1.png differ diff --git a/docs/manual/img/full-import-2.png b/docs/manual/img/full-import-2.png new file mode 100644 index 0000000000..f5cbef66bf Binary files /dev/null and b/docs/manual/img/full-import-2.png differ diff --git a/docs/manual/img/generated-admin-deleted.png b/docs/manual/img/generated-admin-deleted.png new file mode 100644 index 0000000000..99574204af Binary files /dev/null and b/docs/manual/img/generated-admin-deleted.png differ diff --git a/docs/manual/img/global-analytics.png b/docs/manual/img/global-analytics.png new file mode 100644 index 0000000000..ad708affe4 Binary files /dev/null and b/docs/manual/img/global-analytics.png differ diff --git a/docs/manual/img/go-to-admins.png b/docs/manual/img/go-to-admins.png new file mode 100644 index 0000000000..f3f109d0bf Binary files /dev/null and b/docs/manual/img/go-to-admins.png differ diff --git a/docs/manual/img/go-to-danger-zone.png b/docs/manual/img/go-to-danger-zone.png new file mode 100644 index 0000000000..5dc6e83fe2 Binary files /dev/null and b/docs/manual/img/go-to-danger-zone.png differ diff --git a/docs/manual/img/home-page.png b/docs/manual/img/home-page.png new file mode 100644 index 0000000000..f6939a5532 Binary files /dev/null and b/docs/manual/img/home-page.png differ diff --git a/docs/manual/img/jwt-verif-capture.png b/docs/manual/img/jwt-verif-capture.png new file mode 100644 index 0000000000..b56419cac6 Binary files /dev/null and b/docs/manual/img/jwt-verif-capture.png differ diff --git a/docs/manual/img/jwt-verif-global-verifier.png b/docs/manual/img/jwt-verif-global-verifier.png new file mode 100644 index 0000000000..85b10ab8eb Binary files /dev/null and b/docs/manual/img/jwt-verif-global-verifier.png differ diff --git a/docs/manual/img/jwt-verif-global-verifiers.png b/docs/manual/img/jwt-verif-global-verifiers.png new file mode 100644 index 0000000000..ab8c23a948 Binary files /dev/null and b/docs/manual/img/jwt-verif-global-verifiers.png differ diff --git a/docs/manual/img/jwt-verif-incookie.png b/docs/manual/img/jwt-verif-incookie.png new file mode 100644 index 0000000000..59f1eaee2e Binary files /dev/null and b/docs/manual/img/jwt-verif-incookie.png differ diff --git a/docs/manual/img/jwt-verif-inheader.png b/docs/manual/img/jwt-verif-inheader.png new file mode 100644 index 0000000000..239e0b7c8d Binary files /dev/null and b/docs/manual/img/jwt-verif-inheader.png differ diff --git a/docs/manual/img/jwt-verif-inquery.png b/docs/manual/img/jwt-verif-inquery.png new file mode 100644 index 0000000000..adcbd600a1 Binary files /dev/null and b/docs/manual/img/jwt-verif-inquery.png differ diff --git a/docs/manual/img/jwt-verif-ref.png b/docs/manual/img/jwt-verif-ref.png new file mode 100644 index 0000000000..106ede046c Binary files /dev/null and b/docs/manual/img/jwt-verif-ref.png differ diff --git a/docs/manual/img/jwt-verif-resign.png b/docs/manual/img/jwt-verif-resign.png new file mode 100644 index 0000000000..583c8bd1de Binary files /dev/null and b/docs/manual/img/jwt-verif-resign.png differ diff --git a/docs/manual/img/jwt-verif-signing-1.png b/docs/manual/img/jwt-verif-signing-1.png new file mode 100644 index 0000000000..81fe7f9a62 Binary files /dev/null and b/docs/manual/img/jwt-verif-signing-1.png differ diff --git a/docs/manual/img/jwt-verif-signing-2.png b/docs/manual/img/jwt-verif-signing-2.png new file mode 100644 index 0000000000..0d168f9a2b Binary files /dev/null and b/docs/manual/img/jwt-verif-signing-2.png differ diff --git a/docs/manual/img/jwt-verif-transform.png b/docs/manual/img/jwt-verif-transform.png new file mode 100644 index 0000000000..133f5ac8ba Binary files /dev/null and b/docs/manual/img/jwt-verif-transform.png differ diff --git a/docs/manual/img/jwt-verif-verify.png b/docs/manual/img/jwt-verif-verify.png new file mode 100644 index 0000000000..c8411109ba Binary files /dev/null and b/docs/manual/img/jwt-verif-verify.png differ diff --git a/docs/manual/img/login-auth0.png b/docs/manual/img/login-auth0.png new file mode 100644 index 0000000000..512ba1d713 Binary files /dev/null and b/docs/manual/img/login-auth0.png differ diff --git a/docs/manual/img/login-page.png b/docs/manual/img/login-page.png new file mode 100644 index 0000000000..6dee3b22cd Binary files /dev/null and b/docs/manual/img/login-page.png differ diff --git a/docs/manual/img/models-apikey.png b/docs/manual/img/models-apikey.png new file mode 100644 index 0000000000..2f8d8dd5e4 Binary files /dev/null and b/docs/manual/img/models-apikey.png differ diff --git a/docs/manual/img/models-group.png b/docs/manual/img/models-group.png new file mode 100644 index 0000000000..6a71538cbf Binary files /dev/null and b/docs/manual/img/models-group.png differ diff --git a/docs/manual/img/models-service.png b/docs/manual/img/models-service.png new file mode 100644 index 0000000000..7ea4a4641a Binary files /dev/null and b/docs/manual/img/models-service.png differ diff --git a/docs/manual/img/mtls-arch-1.jpg b/docs/manual/img/mtls-arch-1.jpg new file mode 100644 index 0000000000..0e9adc85d6 Binary files /dev/null and b/docs/manual/img/mtls-arch-1.jpg differ diff --git a/docs/manual/img/mtls-arch-2.jpg b/docs/manual/img/mtls-arch-2.jpg new file mode 100644 index 0000000000..54c780f827 Binary files /dev/null and b/docs/manual/img/mtls-arch-2.jpg differ diff --git a/docs/manual/img/mtls-cert-client-backend-1.png b/docs/manual/img/mtls-cert-client-backend-1.png new file mode 100644 index 0000000000..c2c0dbe2e9 Binary files /dev/null and b/docs/manual/img/mtls-cert-client-backend-1.png differ diff --git a/docs/manual/img/mtls-cert-server-frontend-1.png b/docs/manual/img/mtls-cert-server-frontend-1.png new file mode 100644 index 0000000000..4e5374427d Binary files /dev/null and b/docs/manual/img/mtls-cert-server-frontend-1.png differ diff --git a/docs/manual/img/mtls-ff-1.png b/docs/manual/img/mtls-ff-1.png new file mode 100644 index 0000000000..154a6bf590 Binary files /dev/null and b/docs/manual/img/mtls-ff-1.png differ diff --git a/docs/manual/img/mtls-ff-2.png b/docs/manual/img/mtls-ff-2.png new file mode 100644 index 0000000000..8a944482a8 Binary files /dev/null and b/docs/manual/img/mtls-ff-2.png differ diff --git a/docs/manual/img/mtls-ff-3.png b/docs/manual/img/mtls-ff-3.png new file mode 100644 index 0000000000..97636099d2 Binary files /dev/null and b/docs/manual/img/mtls-ff-3.png differ diff --git a/docs/manual/img/mtls-service-1.png b/docs/manual/img/mtls-service-1.png new file mode 100644 index 0000000000..ebe2b630fc Binary files /dev/null and b/docs/manual/img/mtls-service-1.png differ diff --git a/docs/manual/img/mtls-va-1.png b/docs/manual/img/mtls-va-1.png new file mode 100644 index 0000000000..632bce82ab Binary files /dev/null and b/docs/manual/img/mtls-va-1.png differ diff --git a/docs/manual/img/mtls-va-ref-1.png b/docs/manual/img/mtls-va-ref-1.png new file mode 100644 index 0000000000..6f833adbdb Binary files /dev/null and b/docs/manual/img/mtls-va-ref-1.png differ diff --git a/docs/manual/img/new-service-canary.png b/docs/manual/img/new-service-canary.png new file mode 100644 index 0000000000..c616394089 Binary files /dev/null and b/docs/manual/img/new-service-canary.png differ diff --git a/docs/manual/img/new-service-client.png b/docs/manual/img/new-service-client.png new file mode 100644 index 0000000000..7e7b65bbc2 Binary files /dev/null and b/docs/manual/img/new-service-client.png differ diff --git a/docs/manual/img/new-service-flags.png b/docs/manual/img/new-service-flags.png new file mode 100644 index 0000000000..8de5695e52 Binary files /dev/null and b/docs/manual/img/new-service-flags.png differ diff --git a/docs/manual/img/new-service-flags2.png b/docs/manual/img/new-service-flags2.png new file mode 100644 index 0000000000..1a9190ebbf Binary files /dev/null and b/docs/manual/img/new-service-flags2.png differ diff --git a/docs/manual/img/new-service-healthcheck.png b/docs/manual/img/new-service-healthcheck.png new file mode 100644 index 0000000000..f80d072c2d Binary files /dev/null and b/docs/manual/img/new-service-healthcheck.png differ diff --git a/docs/manual/img/new-service-meta.png b/docs/manual/img/new-service-meta.png new file mode 100644 index 0000000000..ed19e497d4 Binary files /dev/null and b/docs/manual/img/new-service-meta.png differ diff --git a/docs/manual/img/new-service-patterns.png b/docs/manual/img/new-service-patterns.png new file mode 100644 index 0000000000..96f0bb8a7d Binary files /dev/null and b/docs/manual/img/new-service-patterns.png differ diff --git a/docs/manual/img/private-sessions.png b/docs/manual/img/private-sessions.png new file mode 100644 index 0000000000..6f3dcd8d7f Binary files /dev/null and b/docs/manual/img/private-sessions.png differ diff --git a/docs/manual/img/push-to-elastic.png b/docs/manual/img/push-to-elastic.png new file mode 100644 index 0000000000..88813c1d10 Binary files /dev/null and b/docs/manual/img/push-to-elastic.png differ diff --git a/docs/manual/img/scripts-1.png b/docs/manual/img/scripts-1.png new file mode 100644 index 0000000000..85069f288e Binary files /dev/null and b/docs/manual/img/scripts-1.png differ diff --git a/docs/manual/img/scripts-2.png b/docs/manual/img/scripts-2.png new file mode 100644 index 0000000000..772d45b901 Binary files /dev/null and b/docs/manual/img/scripts-2.png differ diff --git a/docs/manual/img/sec-com-signing-2-bis.png b/docs/manual/img/sec-com-signing-2-bis.png new file mode 100644 index 0000000000..223fef6749 Binary files /dev/null and b/docs/manual/img/sec-com-signing-2-bis.png differ diff --git a/docs/manual/img/sec-com-signing-2.png b/docs/manual/img/sec-com-signing-2.png new file mode 100644 index 0000000000..6d88f6cbac Binary files /dev/null and b/docs/manual/img/sec-com-signing-2.png differ diff --git a/docs/manual/img/sec-com-signing-bis.png b/docs/manual/img/sec-com-signing-bis.png new file mode 100644 index 0000000000..8ebcd37c6f Binary files /dev/null and b/docs/manual/img/sec-com-signing-bis.png differ diff --git a/docs/manual/img/sec-com-signing.png b/docs/manual/img/sec-com-signing.png new file mode 100644 index 0000000000..b724a9237f Binary files /dev/null and b/docs/manual/img/sec-com-signing.png differ diff --git a/docs/manual/img/service-analytics.png b/docs/manual/img/service-analytics.png new file mode 100644 index 0000000000..9fa513b6c2 Binary files /dev/null and b/docs/manual/img/service-analytics.png differ diff --git a/docs/manual/img/service-flags-3.png b/docs/manual/img/service-flags-3.png new file mode 100644 index 0000000000..6e5ba5d511 Binary files /dev/null and b/docs/manual/img/service-flags-3.png differ diff --git a/docs/manual/img/service-groups-add.png b/docs/manual/img/service-groups-add.png new file mode 100644 index 0000000000..d370d66e9f Binary files /dev/null and b/docs/manual/img/service-groups-add.png differ diff --git a/docs/manual/img/service-groups-create.png b/docs/manual/img/service-groups-create.png new file mode 100644 index 0000000000..e779e38e1e Binary files /dev/null and b/docs/manual/img/service-groups-create.png differ diff --git a/docs/manual/img/service-groups-created.png b/docs/manual/img/service-groups-created.png new file mode 100644 index 0000000000..8e991990d7 Binary files /dev/null and b/docs/manual/img/service-groups-created.png differ diff --git a/docs/manual/img/service-groups-delete-confirm.png b/docs/manual/img/service-groups-delete-confirm.png new file mode 100644 index 0000000000..853a1e2477 Binary files /dev/null and b/docs/manual/img/service-groups-delete-confirm.png differ diff --git a/docs/manual/img/service-groups-delete.png b/docs/manual/img/service-groups-delete.png new file mode 100644 index 0000000000..6dfcd41775 Binary files /dev/null and b/docs/manual/img/service-groups-delete.png differ diff --git a/docs/manual/img/service-groups-edit.png b/docs/manual/img/service-groups-edit.png new file mode 100644 index 0000000000..44f534d57d Binary files /dev/null and b/docs/manual/img/service-groups-edit.png differ diff --git a/docs/manual/img/service-groups-new.png b/docs/manual/img/service-groups-new.png new file mode 100644 index 0000000000..bfd567859a Binary files /dev/null and b/docs/manual/img/service-groups-new.png differ diff --git a/docs/manual/img/service-groups-update.png b/docs/manual/img/service-groups-update.png new file mode 100644 index 0000000000..070289ba1c Binary files /dev/null and b/docs/manual/img/service-groups-update.png differ diff --git a/docs/manual/img/service-groups.png b/docs/manual/img/service-groups.png new file mode 100644 index 0000000000..a77abd1a19 Binary files /dev/null and b/docs/manual/img/service-groups.png differ diff --git a/docs/manual/img/service-healthcheck.png b/docs/manual/img/service-healthcheck.png new file mode 100644 index 0000000000..e299d0db35 Binary files /dev/null and b/docs/manual/img/service-healthcheck.png differ diff --git a/docs/manual/img/service-live-stats.png b/docs/manual/img/service-live-stats.png new file mode 100644 index 0000000000..e4277aa41a Binary files /dev/null and b/docs/manual/img/service-live-stats.png differ diff --git a/docs/manual/img/settings-menu-groups.png b/docs/manual/img/settings-menu-groups.png new file mode 100644 index 0000000000..6373f2f942 Binary files /dev/null and b/docs/manual/img/settings-menu-groups.png differ diff --git a/docs/manual/img/settings-menu.png b/docs/manual/img/settings-menu.png new file mode 100644 index 0000000000..f4ae289970 Binary files /dev/null and b/docs/manual/img/settings-menu.png differ diff --git a/docs/manual/img/sidebar-all-services.png b/docs/manual/img/sidebar-all-services.png new file mode 100644 index 0000000000..b1a606d0f6 Binary files /dev/null and b/docs/manual/img/sidebar-all-services.png differ diff --git a/docs/manual/img/sidebar-apikeys.png b/docs/manual/img/sidebar-apikeys.png new file mode 100644 index 0000000000..7cc11c0a31 Binary files /dev/null and b/docs/manual/img/sidebar-apikeys.png differ diff --git a/docs/manual/img/small-otoroshi-logo-xmas.png b/docs/manual/img/small-otoroshi-logo-xmas.png new file mode 100644 index 0000000000..44812ccc85 Binary files /dev/null and b/docs/manual/img/small-otoroshi-logo-xmas.png differ diff --git a/docs/manual/img/small-otoroshi-logo.png b/docs/manual/img/small-otoroshi-logo.png new file mode 100644 index 0000000000..fc393e3edc Binary files /dev/null and b/docs/manual/img/small-otoroshi-logo.png differ diff --git a/docs/manual/img/snow-monkey-faults.png b/docs/manual/img/snow-monkey-faults.png new file mode 100644 index 0000000000..ff53cac7cb Binary files /dev/null and b/docs/manual/img/snow-monkey-faults.png differ diff --git a/docs/manual/img/snow-monkey-outages.png b/docs/manual/img/snow-monkey-outages.png new file mode 100644 index 0000000000..4381ff45fb Binary files /dev/null and b/docs/manual/img/snow-monkey-outages.png differ diff --git a/docs/manual/img/snow-monkey-settings.png b/docs/manual/img/snow-monkey-settings.png new file mode 100644 index 0000000000..7dcde5873c Binary files /dev/null and b/docs/manual/img/snow-monkey-settings.png differ diff --git a/docs/manual/img/snow-monkey-start.png b/docs/manual/img/snow-monkey-start.png new file mode 100644 index 0000000000..5e0be102fd Binary files /dev/null and b/docs/manual/img/snow-monkey-start.png differ diff --git a/docs/manual/img/snow-monkey.png b/docs/manual/img/snow-monkey.png new file mode 100644 index 0000000000..06742241fb Binary files /dev/null and b/docs/manual/img/snow-monkey.png differ diff --git a/docs/manual/img/ssl.png b/docs/manual/img/ssl.png new file mode 100644 index 0000000000..e849eeb1f7 Binary files /dev/null and b/docs/manual/img/ssl.png differ diff --git a/docs/manual/img/ssl2.png b/docs/manual/img/ssl2.png new file mode 100644 index 0000000000..5698db4e55 Binary files /dev/null and b/docs/manual/img/ssl2.png differ diff --git a/docs/manual/img/tls13.png b/docs/manual/img/tls13.png new file mode 100644 index 0000000000..4c53aaa632 Binary files /dev/null and b/docs/manual/img/tls13.png differ diff --git a/docs/manual/index.html b/docs/manual/index.html new file mode 100644 index 0000000000..79e27274c3 --- /dev/null +++ b/docs/manual/index.html @@ -0,0 +1,382 @@ + + + + +Otoroshi + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+ + + +
+
+

Otoroshi

+

Otoroshi is a layer of lightweight api management on top of a modern http reverse proxy written in Scala and developped by the MAIF OSS team that can handle all the calls to and between your microservices without service locator and let you change configuration dynamicaly at runtime.

+
+

The Otoroshi is a large hairy monster that tends to lurk on the top of the torii gate in front of Shinto shrines. It’s a hostile creature, but also said to be the guardian of the shrine and is said to leap down from the top of the gate to devour those who approach the shrine for only self-serving purposes.

+
+

Build Status Join the chat at https://gitter.im/MAIF/otoroshi Download

+
+

Installation

+

You can download the latest build of Otoroshi as a fat jar, as a zip package or as a docker image.

+

You can install and run Otoroshi with this little bash snippet

+
curl -L -o otoroshi.jar 'https://github.com/MAIF/otoroshi/releases/download/v1.4.18/otoroshi.jar'
+java -jar otoroshi.jar
+
+

or using docker

+
docker run -p "8080:8080" maif/otoroshi:1.4.18
+
+

now open your browser to http://otoroshi.oto.tools:8080/, log in with the credential generated in the logs and explore by yourself, if you want better instructions, just go to the Quick Start or directly to the installation instructions

+

Documentation

+ +

Discussion

+

Join the Otoroshi channel on the MAIF Gitter

+

Sources

+

The sources of Otoroshi are available on Github.

+

Logo

+

You can find the official Otoroshi logo on GitHub. The Otoroshi logo has been created by François Galioto (@fgalioto)

+

Changelog

+

Every release, along with the migration instructions, is documented on the Github Releases page.

+

Patrons

+

The work on Otoroshi was funded by MAIF with the help of the community.

+

Licence

+

Otoroshi is Open Source and available under the Apache 2 License

+ +
+
+ +
+
+ +
+
+ +
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + diff --git a/docs/manual/integrations/analytics.html b/docs/manual/integrations/analytics.html new file mode 100644 index 0000000000..62bb22dba1 --- /dev/null +++ b/docs/manual/integrations/analytics.html @@ -0,0 +1,359 @@ + + + + +Analytics · Otoroshi + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+ + + +
+
+

Analytics

+

Each action and request on Otoroshi creates events that can be sent outside of Otoroshi for further usage. Those events can be sent using a webhook and/or through a Kafka topic.

+

Push events to Elasticsearch

+

You can use elastic search to store otoroshi events. To do this you have to configure the access to elasticsearch from settings (cog icon) / Danger Zone and expand the Analytics: Elastic cluster (write) section.

+
+

Read events from Elasticsearch

+

You can use elastic search to store otoroshi events. To do this you have to configure the access to elasticsearch from settings (cog icon) / Danger Zone and expand the Analytics: Elastic dashboard datasource (read) section.

+
+

Push events to WebHooks

+

Go to settings (cog icon) / Danger Zone and expand the Analytics: Webhooks section.

+
+

Here you can configure the URL of the webhook and its headers if needed.

+

Push events to Kafka

+

Events can also be sent through a Kafka topic. Go to settings (cog icon) / Danger Zone and expand the Analytics: Kafka section.

+
+

Fill the form, default values for topic names are :

+
    +
  • otoroshi-alerts
  • +
  • otoroshi-analytics
  • +
  • otoroshi-audits
  • +
Warning
+

If you use trustore/keystore to access your kafka instances, the paths should be absolute and refers to host paths.

+ +
+ +
+ +
+
+ +
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + diff --git a/docs/manual/integrations/auth0.html b/docs/manual/integrations/auth0.html new file mode 100644 index 0000000000..e0923bd476 --- /dev/null +++ b/docs/manual/integrations/auth0.html @@ -0,0 +1,186 @@ + + + + +Auth0 + + + + + + + + + + + + + + + +
+
+ +
+ +
+ +
+ + + +
+ +
+ +
+ +
+
+ + + +
+
+

Auth0

+

You can use Auth0 to log into Otoroshi’s backoffice and Otoroshi’s private apps.

+

Go to settings (cog icon) / Danger Zone and expand the Backoffice Auth0 settings and Private apps Auth0 settings sections.

+
+

Now, you can fill the following fields :

+
    +
  • Client Id
  • +
  • Client Service
  • +
  • Domain
  • +
+

For the Callback URL fields, use something like

+
https://otoroshi.oto.tools/backoffice/auth0/callback
+https://privateapps.oto.tools/privateapps/auth0/callback
+
+

Of course, you need to replace otoroshi.oto.tools and privateapps.oto.tools with your own domain and sub-domains. Don’t forget to customize the callback URLs in your Auth0 backoffice too.

+

Now if you logout, you will see the Auth0 option on the login screen

+
+
+
+
+
+ +
+
+ +
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + diff --git a/docs/manual/integrations/clevercloud.html b/docs/manual/integrations/clevercloud.html new file mode 100644 index 0000000000..09b64c1e41 --- /dev/null +++ b/docs/manual/integrations/clevercloud.html @@ -0,0 +1,331 @@ + + + + +Clever Cloud · Otoroshi + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+ + + +
+
+

Clever Cloud

+

Otoroshi provides an integration with Clever Cloud to create easily services based on application deployed on your Clever Cloud account. Go to settings (cog icon) / Danger Zone and expand the CleverCloud settings section.

+
+

Fill the form with your CleverCloud credentials (https://www.clever-cloud.com/doc/clever-cloud-apis/cc-api/) and your CleverCloud organization id.

+

Once it’s done, you will see a new menu in the side bar.

+
+

If you click on it, you’ll see a page listing all your apps deployed on Clever Cloud with buttons to create new services with the app as the target.

+
+

You will also see a new button in the Target section of services to attach Clever Cloud applications as target for a service.

+
+ +
+
+
+
+ +
+
+ +
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + diff --git a/docs/manual/integrations/index.html b/docs/manual/integrations/index.html new file mode 100644 index 0000000000..42de7a5915 --- /dev/null +++ b/docs/manual/integrations/index.html @@ -0,0 +1,322 @@ + + + + +Third party Integrations · Otoroshi + + + + + + + + + + + + + + + +
+
+ + + + +
+
+ + + + + + + + + + + + + + + + + + diff --git a/docs/manual/integrations/mailgun.html b/docs/manual/integrations/mailgun.html new file mode 100644 index 0000000000..b972413ced --- /dev/null +++ b/docs/manual/integrations/mailgun.html @@ -0,0 +1,327 @@ + + + + +Mailgun · Otoroshi + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+ + + +
+
+

Mailgun

+

If you want to receive Otoroshi alert by emails, you have to configure Otoroshi with your Mailgun credentials. Go to settings (cog icon) / Danger Zone and expand the Mailgun settings section.

+
+

Fill the form with provided information on the domain informations page on Mailgun located at https://app.mailgun.com/app/domains/my.domain.

+

Then, expand the Alert settings section and add email addresses separated by comma in the Alert emails field. Don’t forget to save.

+
+ +
+
+
+
+ +
+
+ +
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + diff --git a/docs/manual/integrations/statsd.html b/docs/manual/integrations/statsd.html new file mode 100644 index 0000000000..1e44ab81bb --- /dev/null +++ b/docs/manual/integrations/statsd.html @@ -0,0 +1,325 @@ + + + + +StatsD / Datadog · Otoroshi + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+ + + +
+
+

StatsD / Datadog

+

Otoroshi provides a StatsD integration to monitor some technical metrics across all your Otoroshi instances. Go to settings (cog icon) / Danger Zone and expand the Statsd settings section.

+
+

Add the host and port of the Statsd agent on your system. If you’re using Datadog, don’t forget to check the Datadog switch.

+ +
+
+
+
+ +
+
+ +
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + diff --git a/docs/manual/js/groups.js b/docs/manual/js/groups.js new file mode 100644 index 0000000000..e74e7b244e --- /dev/null +++ b/docs/manual/js/groups.js @@ -0,0 +1,179 @@ +groupChangeListeners = []; + +window.groupChanged = function(callback) { + groupChangeListeners.push(callback); +} + +$(function() { + + // Groups (like 'java' and 'scala') represent groups of 'switchable' content, either in tabs or in regular text. + // The catalog of groups can be defined in the sbt parameters to initialize the group. + + var groupCookie = "paradoxGroups"; + var cookieTg = getCookie(groupCookie); + var currentGroups = {}; + + var catalog = {} + var supergroupByGroup = {}; + + if(cookieTg != "") + currentGroups = JSON.parse(cookieTg); + + // http://www.w3schools.com/js/js_cookies.asp + function setCookie(cname,cvalue,exdays) { + if(!exdays) exdays = 365; + var d = new Date(); + d.setTime(d.getTime() + (exdays*24*60*60*1000)); + var expires = "expires=" + d.toGMTString(); + document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/"; + } + + // http://www.w3schools.com/js/js_cookies.asp + function getCookie(cname) { + var name = cname + "="; + var decodedCookie = decodeURIComponent(document.cookie); + var ca = decodedCookie.split(';'); + for(var i = 0; i < ca.length; i++) { + var c = ca[i]; + while (c.charAt(0) == ' ') { + c = c.substring(1); + } + if (c.indexOf(name) == 0) { + return c.substring(name.length, c.length); + } + } + return ""; + } + + $("dl").has("dd > pre").each(function() { + var dl = $(this); + dl.addClass("tabbed"); + var dts = dl.find("dt"); + dts.each(function(i) { + var dt = $(this); + dt.html("" + dt.text() + ""); + }); + var dds = dl.find("dd"); + dds.each(function(i) { + var dd = $(this); + dd.hide(); + if (dd.find("blockquote").length) { + dd.addClass("has-note"); + } + }); + + // Default to the first tab, for grouped tabs switch again later + switchToTab(dts.first()); + + dts.first().addClass("first"); + dts.last().addClass("last"); + }); + + // Determine all supergroups, populate 'catalog' and 'supergroupByGroup' accordingly. + $(".supergroup").each(function() { + var supergroup = $(this).attr('name').toLowerCase(); + var groups = $(this).find(".group"); + + catalog[supergroup] = []; + + groups.each(function() { + var group = "group-" + $(this).text().toLowerCase(); + catalog[supergroup].push(group); + supergroupByGroup[group] = supergroup; + }); + + $(this).on("change", function() { + switchToGroup(supergroup, this.value); + }); + }); + + // Switch to the right initial groups + for (var supergroup in catalog) { + var current = queryParamGroup(supergroup) || currentGroups[supergroup] || catalog[supergroup][0]; + + switchToGroup(supergroup, current); + } + + $("dl.tabbed dt a").click(function(e){ + e.preventDefault(); + var currentDt = $(this).parent("dt"); + var currentDl = currentDt.parent("dl"); + + var currentGroup = groupOf(currentDt); + + var supergroup = supergroupByGroup[currentGroup] + if (supergroup) { + switchToGroup(supergroup, currentGroup); + } else { + switchToTab(currentDt); + } + }); + + function queryParamGroup(supergroup) { + var value = new URLSearchParams(window.location.search).get(supergroup) + if (value) { + return "group-" + value.toLowerCase(); + } else { + return ""; + } + } + + function switchToGroup(supergroup, group) { + currentGroups[supergroup] = group; + setCookie(groupCookie, JSON.stringify(currentGroups)); + + // Dropdown switcher: + $("select") + .has("option[value=" + group +"]") + .val(group); + + // Inline snippets: + for (var i = 0; i < catalog[supergroup].length; i++) { + var peer = catalog[supergroup][i]; + if (peer == group) { + $("." + group).show(); + } else { + $("." + peer).hide(); + } + } + + // Tabbed snippets: + $("dl.tabbed").each(function() { + var dl = $(this); + dl.find("dt").each(function() { + var dt = $(this); + if(groupOf(dt) == group) { + switchToTab(dt); + } + }); + }); + + for (var i = 0; i < groupChangeListeners.length; i++) { + groupChangeListeners[i](group, supergroup, catalog); + } + } + + function switchToTab(dt) { + var dl = dt.parent("dl"); + dl.find(".current").removeClass("current").next("dd").removeClass("current").hide(); + dt.addClass("current"); + var currentContent = dt.next("dd").addClass("current").show(); + dl.css("height", dt.height() + currentContent.height()); + } + + function groupOf(elem) { + var classAttribute = elem.next("dd").find("pre").attr("class"); + if (classAttribute) { + var currentClasses = classAttribute.split(' '); + var regex = new RegExp("^group-.*"); + for(var i = 0; i < currentClasses.length; i++) { + if(regex.test(currentClasses[i])) { + return currentClasses[i]; + } + } + } + + // No class found? Then use the tab title + return "group-" + elem.find('a').text().toLowerCase(); + } +}); diff --git a/docs/manual/js/magellan.js b/docs/manual/js/magellan.js new file mode 100644 index 0000000000..52d231ecfb --- /dev/null +++ b/docs/manual/js/magellan.js @@ -0,0 +1,25 @@ +$(function() { + + // add magellan targets to anchor headers, up to depth 3 + $("a.anchor").each(function() { + var anchor = $(this); + var name = anchor.attr("name"); + var header = anchor.parent(); + if (header.is("h1") || header.is("h2") || header.is("h3")) { + header.attr("id", name).attr("data-magellan-target", name); + } + }); + + // enable magellan plugin on the page navigation + $(".page-nav").each(function() { + var nav = $(this); + + // strip page navigation links down to just the hash fragment + nav.find("a").attr('href', function(_, current){ + return this.hash ? this.hash : current; + }); + + new Foundation.Magellan(nav); + }); + +}); diff --git a/docs/manual/js/page.js b/docs/manual/js/page.js new file mode 100644 index 0000000000..3b81dd518e --- /dev/null +++ b/docs/manual/js/page.js @@ -0,0 +1,56 @@ +$(function() { + $('.swagger-frame').each(function() { + $(this).append(' +

Use CLI to create load balanced services with retry

+

here you will learn how to create a service using the Otoroshi CLI and how to use load balancing with retry features of Otoroshi.

+ + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/manual/src/main/paradox/content-pretty.json b/manual/src/main/paradox/content-pretty.json index 38972cdb9a..62abeb2c61 100644 --- a/manual/src/main/paradox/content-pretty.json +++ b/manual/src/main/paradox/content-pretty.json @@ -109,7 +109,7 @@ "id": "/firstrun/configfile.md", "url": "/firstrun/configfile.html", "title": "Config. with files", - "content": "# Config. with files\n\nThere is a lot of things you can configure in Otoroshi. By default, Otoroshi provides a configuration that should be enough for testing purpose. But you'll likely need to update this configuration when you'll need to move into production.\n\nIn this page, any configuration property can be set at runtime using a `-D` flag when launching Otoroshi like\n\n```sh\njava -Dapp.domain=oto.tools -Dhttp.port=8080 -jar otoroshi.jar\n```\n\nor\n\n```sh\n./bin/otoroshi -Dhttp.port=8080 -Dapp.domain=oto.tools \n```\n\n## Common configuration\n\n| name | type | default value | description |\n| ---- |:----:| -------------- | ----- |\n| `app.domain` | string | \"oto.tools\" | the domain on which Otoroshi UI/API is be exposed|\n| `app.rootScheme` | string | \"http\" | the scheme on which Otoroshi is exposed, either \"http\" or \"https\" |\n| `app.snowflake.seed` | number | 0 | this number will is used to generate unique ids across the cluster. Each Otorshi instance must have a unique seed. |\n| `app.events.maxSize` | number | 1000 | max number of analytic and alert events stored locally |\n| `app.backoffice.exposed` | boolean | true | does the current Otoroshi instance exposed a backoffice ui|\n| `app.backoffice.subdomain` | string | \"otoroshi\" | the subdomain on wich Otoroshi backoffice will be served |\n| `app.backoffice.session.exp` | number | 86400000 | the number of seconds before the Otoroshi backoffice session expires |\n| `app.privateapps.subdomain` | string | \"privateapps\" | the subdomain on which private apps UI are served |\n| `app.privateapps.session.exp` | number | 86400000 | the number of seconds before the private apps session expires |\n| `app.claim.sharedKey` | string | \"secret\" | the shared secret used for signing the JWT token passed between Otoroshi and backend services |\n| `app.webhooks.size` | number | 100 | number of events sent at most when calling one of the analytics webhooks |\n| `app.throttlingWindow` | number | 10 | time window (in seconds) used to compute throttling quotas for ApiKeys |\n\n## Admin API configuration\n\nWhen Otoroshi starts for the first time, its datastore is empty. As Otoroshi uses Otoroshi to expose its admin REST API, you'll have to provide the details for the admin API exposition. **This part is super important** because if you go to production with the default values, your Otoroshi server won't be secured anymore.\n\n@@@ warning\nYOU HAVE TO CUSTOMIZE THE FOLLOWING VALUES BEFORE GOING TO PRODUCTION !!\n@@@\n\nSome of the following terms will seem obscure to you, but you will learn their meaning in the following chapters :)\n\n| name | type | default value | description |\n| ---- |:----:| -------------- | ----- |\n| `app.adminapi.exposed` | boolean | true | does the current Otoroshi instance expose an admin API |\n| `app.adminapi.targetSubdomain` | string | \"otoroshi-admin-internal-api\" | the subdomain on wich admin API call will be redirected from `app.adminapi.exposedSubdomain` |\n| `app.adminapi.exposedSubdomain` | string | \"otoroshi-api\" | the subdomain on wich the Otoroshi admin API will be exposed |\n| `app.adminapi.defaultValues.backOfficeGroupId` | string | \"admin-api-group\" | the name of the service groups that will contain the service descriptors for the Otoroshi admin API |\n| `app.adminapi.defaultValues.backOfficeApiKeyClientId` | string | \"admin-api-apikey-id\" | the client id of the Otoroshi admin API apikey |\n| `app.adminapi.defaultValues.backOfficeApiKeyClientSecret` | string | \"admin-api-apikey-secret\" | the client secret of the Otoroshi admin API apikey |\n| `app.adminapi.defaultValues.backOfficeServiceId` | string | \"admin-api-service\" | the id of the service descriptors for the Otoroshi admin API |\n| `app.adminapi.proxy.https` | boolean | false | whether or not the current Otoroshi instance serves its content over https. This setting is useful for the backoffice UI to access Otoroshi admin API |\n| `app.adminapi.proxy.local` | boolean | true | whether or not the admin API is accessible through `127.0.0.1`. This setting is useful for the backoffice UI to access Otoroshi admin API |\n\n## DB configuration\n\nAs Otoroshi supports multiple datastores, you'll have to provide some details about how to connect/configure it.\n\n| name | type | default value | description |\n| ---- |:----:| -------------- | ----- |\n| `app.storage` | string | \"inmemory\" | what kind of storage engine you want to use. Possible values are `inmemory`, `leveldb`, `redis`, `cassandra`, `mongo` |\n| `app.importFrom` | string | | a file path or a URL to an Otoroshi export file. If the datastore is empty on startup, this file will be used to import data to the empty DB |\n| `app.importFromHeaders` | array | [] | a list of `:` separated header to use if the `app.importFrom` setting is a URL |\n| `app.initialData` | object |  | object representing Otoroshi internal data as exported from danger zone so you don't need a config file and a data import file |\n| `app.redis.host` | string | \"localhost\" | the host of the redis server |\n| `app.redis.port` | number | 6379 | the port of the redis server |\n| `app.redis.slaves` | array | [] | the redis slaves lists |\n| `app.leveldb.path` | string | \"./leveldb\" | the path where levelDB files will be written |\n| `app.cassandra.hosts` | string | \"127.0.0.1\" | the host of the cassandra server |\n| `app.cassandra.host` | string | \"127.0.0.1\" | the list of cassandra hosts |\n| `app.cassandra.port` | number | 9042 | the port of the cassandra servers |\n| `app.mongo.uri` | string | \"mongodb://localhost:27017/default\" | the mongo URI following Mongo semantic https://docs.mongodb.com/manual/reference/connection-string/ |\n\n## Headers configuration\n\nOtoroshi uses a fair amount of http headers in order to work properly. The name of those headers are customizable to fit your needs.\n\n| name | type | default value | description |\n| ---- |:----:| -------------- | ----- |\n| `otoroshi.headers.trace.label` | string | \"Otoroshi-Viz-From-Label\" | header to pass request tracing informations |\n| `otoroshi.headers.trace.from` | string | \"Otoroshi-Viz-From\" | header to pass request tracing informations (ip address) |\n| `otoroshi.headers.trace.parent` | string | \"Otoroshi-Parent-Request\" | header to pass request tracing informations (parent request id) |\n| `otoroshi.headers.request.adminprofile` | string | \"Otoroshi-Admin-Profile\" | header to pass admin name when the admin API is called from the Otoroshi backoffice |\n| `otoroshi.headers.request.clientid` | string | \"Otoroshi-Client-Id\" | header to pass apikey client id |\n| `otoroshi.headers.request.clientsecret` | string | \"Otoroshi-Client-Secret\" | header to pass apikey client secret |\n| `otoroshi.headers.request.id` | string | \"Otoroshi-Request-Id\" | header containing the id of the current request |\n| `otoroshi.headers.response.proxyhost` | string | \"Otoroshi-Proxied-Host\" | header containing the proxied host |\n| `otoroshi.headers.response.error` | string | \"Otoroshi-Error\" | header containing whether or not the request generated an error |\n| `otoroshi.headers.response.errormsg` | string | \"Otoroshi-Error-Msg\" | header containing error message if some |\n| `otoroshi.headers.response.proxylatency` | string | \"Otoroshi-Proxy-Latency\" | header containing the current latency induced by Otoroshi |\n| `otoroshi.headers.response.upstreamlatency` | string | \"Otoroshi-Upstream-Latency\" | header containing the current latency from Otoroshi to service backend |\n| `otoroshi.headers.response.dailyquota` | string | \"Otoroshi-Daily-Calls-Remaining\" | header containing the number of remaining daily call (apikey) |\n| `otoroshi.headers.response.monthlyquota` | string | \"Otoroshi-Monthly-Calls-Remaining\" | header containing the number of remaining monthly call (apikey) |\n| `otoroshi.headers.comm.state` | string | \"Otoroshi-State\" | header containing a random value for secured mode |\n| `otoroshi.headers.comm.stateresp` | string | \"Otoroshi-State-Resp\" | header containing a random value for secured mode |\n| `otoroshi.headers.comm.claim` | string | \"Otoroshi-Claim\" | header containing a JWT token for secured mode |\n| `otoroshi.headers.healthcheck.test` | string | \"Otoroshi-Health-Check-Logic-Test\" | header containing a logic test for healthcheck |\n| `otoroshi.headers.healthcheck.testresult` | string | \"Otoroshi-Health-Check-Logic-Test-Result\" | header containing the result of a logic test for healthcheck |\n| `otoroshi.headers.jwt.issuer` | string | \"Otoroshi\" | the name of the issuer for the JWT token |\n| `otoroshi.headers.canary.tracker` | string | \"Otoroshi-Canary-Id\" | header containing the ID of the canary session if enabled |\n\n## Play specific configuration\n\nAs Otoroshi is a [Play app](https://www.playframework.com/), you should take a look at [Play configuration documentation](https://www.playframework.com/documentation/2.6.x/Configuration) to tune its internal configuration\n\n| name | type | default value | description |\n| ---- |:----:| -------------- | ----- |\n| `http.port` | number | 8080 | the http port used by Otoroshi. You can use 'disabled' as value if you don't want to use http |\n| `https.port` | number | disabled | the https port used by Otoroshi. You can use 'disabled' as value if you don't want to use https |\n| `http2.enabled` | boolean | false | whether or not http2 is enabled on the Otoroshi server. You need to configure https (listed bellow) to be able to use it |\n| `play.http.secret.key` | string | \"secret\" | the secret used to sign Otoroshi session cookie |\n| `play.http.session.secure` | boolean | false | whether or not the Otoroshi backoffice session will be served over https only |\n| `play.http.session.httpOnly` | boolean | true | whether or not the Otoroshi backoffice session will be accessible from Javascript |\n| `play.http.session.maxAge` | number | 259200000 | the number of seconds before Otoroshi backoffice session expired |\n| `play.http.session.domain` | string | \".oto.tools\" | the domain on which the Otoroshi backoffice session is authorized |\n| `play.http.session.cookieName` | string | \"otoroshi-session\" | the name of the Otoroshi backoffice session |\n| `play.ws.play.ws.useragent` | string | \"Otoroshi\" | the user agent sent by Otoroshi if not present on the original http request |\n| `play.server.https.keyStore.path` | string | | the path to the keystore containing the private key and certificate, if not provided generates a keystore for you |\n| `play.server.https.keyStore.type` | string | JKS | the key store type, defaults to JKS |\n| `play.server.https.keyStore.password` | string | '' | the password, defaults to a blank password |\n| `play.server.https.keyStore.algorithm` | string | | the key store algorithm, defaults to the platforms default algorithm |\n\n## More config. options\n\nSee https://github.com/MAIF/otoroshi/blob/master/otoroshi/conf/base.conf and https://github.com/MAIF/otoroshi/blob/master/otoroshi/conf/application.conf\n\nif you want to configure https on your Otoroshi server, just read [PlayFramework documentation about it](https://www.playframework.com/documentation/2.6.x/ConfiguringHttps)\n\n## Example of configuration file\n\n```conf\ninclude \"application.conf\"\n\nhttp.port = 8080\n\napp {\n storage = \"leveldb\"\n importFrom = \"./my-state.json\"\n env = \"prod\"\n domain = \"oto.tools\"\n rootScheme = \"http\"\n snowflake {\n seed = 0\n }\n events {\n maxSize = 1000\n }\n backoffice {\n subdomain = \"otoroshi\"\n session {\n exp = 86400000\n }\n }\n privateapps {\n subdomain = \"privateapps\"\n session {\n exp = 86400000\n }\n }\n adminapi {\n targetSubdomain = \"otoroshi-admin-internal-api\"\n exposedSubdomain = \"otoroshi-api\"\n defaultValues {\n backOfficeGroupId = \"admin-api-group\"\n backOfficeApiKeyClientId = \"admin-api-apikey-id\"\n backOfficeApiKeyClientSecret = \"admin-api-apikey-secret\"\n backOfficeServiceId = \"admin-api-service\"\n }\n }\n claim {\n sharedKey = \"mysecret\"\n }\n leveldb {\n path = \"./leveldb\"\n }\n}\n\nplay.http {\n session {\n secure = false\n httpOnly = true\n maxAge = 2592000000\n domain = \".oto.tools\"\n cookieName = \"oto-sess\"\n }\n}\n```\n" + "content": "# Config. with files\n\nThere is a lot of things you can configure in Otoroshi. By default, Otoroshi provides a configuration that should be enough for testing purpose. But you'll likely need to update this configuration when you'll need to move into production.\n\nIn this page, any configuration property can be set at runtime using a `-D` flag when launching Otoroshi like\n\n```sh\njava -Dhttp.port=8080 -jar otoroshi.jar\n```\n\nor\n\n```sh\n./bin/otoroshi -Dhttp.port=8080 \n```\n\n## Common configuration\n\n| name | type | default value | description |\n| ---- |:----:| -------------- | ----- |\n| `app.domain` | string | \"oto.tools\" | the domain on which Otoroshi UI/API is be exposed|\n| `app.rootScheme` | string | \"http\" | the scheme on which Otoroshi is exposed, either \"http\" or \"https\" |\n| `app.snowflake.seed` | number | 0 | this number will is used to generate unique ids across the cluster. Each Otorshi instance must have a unique seed. |\n| `app.events.maxSize` | number | 1000 | max number of analytic and alert events stored locally |\n| `app.backoffice.exposed` | boolean | true | does the current Otoroshi instance exposed a backoffice ui|\n| `app.backoffice.subdomain` | string | \"otoroshi\" | the subdomain on wich Otoroshi backoffice will be served |\n| `app.backoffice.session.exp` | number | 86400000 | the number of seconds before the Otoroshi backoffice session expires |\n| `app.privateapps.subdomain` | string | \"privateapps\" | the subdomain on which private apps UI are served |\n| `app.privateapps.session.exp` | number | 86400000 | the number of seconds before the private apps session expires |\n| `app.claim.sharedKey` | string | \"secret\" | the shared secret used for signing the JWT token passed between Otoroshi and backend services |\n| `app.webhooks.size` | number | 100 | number of events sent at most when calling one of the analytics webhooks |\n| `app.throttlingWindow` | number | 10 | time window (in seconds) used to compute throttling quotas for ApiKeys |\n\n## Admin API configuration\n\nWhen Otoroshi starts for the first time, its datastore is empty. As Otoroshi uses Otoroshi to expose its admin REST API, you'll have to provide the details for the admin API exposition. **This part is super important** because if you go to production with the default values, your Otoroshi server won't be secured anymore.\n\n@@@ warning\nYOU HAVE TO CUSTOMIZE THE FOLLOWING VALUES BEFORE GOING TO PRODUCTION !!\n@@@\n\nSome of the following terms will seem obscure to you, but you will learn their meaning in the following chapters :)\n\n| name | type | default value | description |\n| ---- |:----:| -------------- | ----- |\n| `app.adminapi.exposed` | boolean | true | does the current Otoroshi instance expose an admin API |\n| `app.adminapi.targetSubdomain` | string | \"otoroshi-admin-internal-api\" | the subdomain on wich admin API call will be redirected from `app.adminapi.exposedSubdomain` |\n| `app.adminapi.exposedSubdomain` | string | \"otoroshi-api\" | the subdomain on wich the Otoroshi admin API will be exposed |\n| `app.adminapi.defaultValues.backOfficeGroupId` | string | \"admin-api-group\" | the name of the service groups that will contain the service descriptors for the Otoroshi admin API |\n| `app.adminapi.defaultValues.backOfficeApiKeyClientId` | string | \"admin-api-apikey-id\" | the client id of the Otoroshi admin API apikey |\n| `app.adminapi.defaultValues.backOfficeApiKeyClientSecret` | string | \"admin-api-apikey-secret\" | the client secret of the Otoroshi admin API apikey |\n| `app.adminapi.defaultValues.backOfficeServiceId` | string | \"admin-api-service\" | the id of the service descriptors for the Otoroshi admin API |\n| `app.adminapi.proxy.https` | boolean | false | whether or not the current Otoroshi instance serves its content over https. This setting is useful for the backoffice UI to access Otoroshi admin API |\n| `app.adminapi.proxy.local` | boolean | true | whether or not the admin API is accessible through `127.0.0.1`. This setting is useful for the backoffice UI to access Otoroshi admin API |\n\n## DB configuration\n\nAs Otoroshi supports multiple datastores, you'll have to provide some details about how to connect/configure it.\n\n| name | type | default value | description |\n| ---- |:----:| -------------- | ----- |\n| `app.storage` | string | \"inmemory\" | what kind of storage engine you want to use. Possible values are `inmemory`, `leveldb`, `redis`, `cassandra`, `mongo` |\n| `app.importFrom` | string | | a file path or a URL to an Otoroshi export file. If the datastore is empty on startup, this file will be used to import data to the empty DB |\n| `app.importFromHeaders` | array | [] | a list of `:` separated header to use if the `app.importFrom` setting is a URL |\n| `app.initialData` | object |  | object representing Otoroshi internal data as exported from danger zone so you don't need a config file and a data import file |\n| `app.redis.host` | string | \"localhost\" | the host of the redis server |\n| `app.redis.port` | number | 6379 | the port of the redis server |\n| `app.redis.slaves` | array | [] | the redis slaves lists |\n| `app.leveldb.path` | string | \"./leveldb\" | the path where levelDB files will be written |\n| `app.cassandra.hosts` | string | \"127.0.0.1\" | the host of the cassandra server |\n| `app.cassandra.host` | string | \"127.0.0.1\" | the list of cassandra hosts |\n| `app.cassandra.port` | number | 9042 | the port of the cassandra servers |\n| `app.mongo.uri` | string | \"mongodb://localhost:27017/default\" | the mongo URI following Mongo semantic https://docs.mongodb.com/manual/reference/connection-string/ |\n\n## Headers configuration\n\nOtoroshi uses a fair amount of http headers in order to work properly. The name of those headers are customizable to fit your needs.\n\n| name | type | default value | description |\n| ---- |:----:| -------------- | ----- |\n| `otoroshi.headers.trace.label` | string | \"Otoroshi-Viz-From-Label\" | header to pass request tracing informations |\n| `otoroshi.headers.trace.from` | string | \"Otoroshi-Viz-From\" | header to pass request tracing informations (ip address) |\n| `otoroshi.headers.trace.parent` | string | \"Otoroshi-Parent-Request\" | header to pass request tracing informations (parent request id) |\n| `otoroshi.headers.request.adminprofile` | string | \"Otoroshi-Admin-Profile\" | header to pass admin name when the admin API is called from the Otoroshi backoffice |\n| `otoroshi.headers.request.clientid` | string | \"Otoroshi-Client-Id\" | header to pass apikey client id |\n| `otoroshi.headers.request.clientsecret` | string | \"Otoroshi-Client-Secret\" | header to pass apikey client secret |\n| `otoroshi.headers.request.id` | string | \"Otoroshi-Request-Id\" | header containing the id of the current request |\n| `otoroshi.headers.response.proxyhost` | string | \"Otoroshi-Proxied-Host\" | header containing the proxied host |\n| `otoroshi.headers.response.error` | string | \"Otoroshi-Error\" | header containing whether or not the request generated an error |\n| `otoroshi.headers.response.errormsg` | string | \"Otoroshi-Error-Msg\" | header containing error message if some |\n| `otoroshi.headers.response.proxylatency` | string | \"Otoroshi-Proxy-Latency\" | header containing the current latency induced by Otoroshi |\n| `otoroshi.headers.response.upstreamlatency` | string | \"Otoroshi-Upstream-Latency\" | header containing the current latency from Otoroshi to service backend |\n| `otoroshi.headers.response.dailyquota` | string | \"Otoroshi-Daily-Calls-Remaining\" | header containing the number of remaining daily call (apikey) |\n| `otoroshi.headers.response.monthlyquota` | string | \"Otoroshi-Monthly-Calls-Remaining\" | header containing the number of remaining monthly call (apikey) |\n| `otoroshi.headers.comm.state` | string | \"Otoroshi-State\" | header containing a random value for secured mode |\n| `otoroshi.headers.comm.stateresp` | string | \"Otoroshi-State-Resp\" | header containing a random value for secured mode |\n| `otoroshi.headers.comm.claim` | string | \"Otoroshi-Claim\" | header containing a JWT token for secured mode |\n| `otoroshi.headers.healthcheck.test` | string | \"Otoroshi-Health-Check-Logic-Test\" | header containing a logic test for healthcheck |\n| `otoroshi.headers.healthcheck.testresult` | string | \"Otoroshi-Health-Check-Logic-Test-Result\" | header containing the result of a logic test for healthcheck |\n| `otoroshi.headers.jwt.issuer` | string | \"Otoroshi\" | the name of the issuer for the JWT token |\n| `otoroshi.headers.canary.tracker` | string | \"Otoroshi-Canary-Id\" | header containing the ID of the canary session if enabled |\n\n## Play specific configuration\n\nAs Otoroshi is a [Play app](https://www.playframework.com/), you should take a look at [Play configuration documentation](https://www.playframework.com/documentation/2.6.x/Configuration) to tune its internal configuration\n\n| name | type | default value | description |\n| ---- |:----:| -------------- | ----- |\n| `http.port` | number | 8080 | the http port used by Otoroshi. You can use 'disabled' as value if you don't want to use http |\n| `https.port` | number | disabled | the https port used by Otoroshi. You can use 'disabled' as value if you don't want to use https |\n| `http2.enabled` | boolean | false | whether or not http2 is enabled on the Otoroshi server. You need to configure https (listed bellow) to be able to use it |\n| `play.http.secret.key` | string | \"secret\" | the secret used to sign Otoroshi session cookie |\n| `play.http.session.secure` | boolean | false | whether or not the Otoroshi backoffice session will be served over https only |\n| `play.http.session.httpOnly` | boolean | true | whether or not the Otoroshi backoffice session will be accessible from Javascript |\n| `play.http.session.maxAge` | number | 259200000 | the number of seconds before Otoroshi backoffice session expired |\n| `play.http.session.domain` | string | \".oto.tools\" | the domain on which the Otoroshi backoffice session is authorized |\n| `play.http.session.cookieName` | string | \"otoroshi-session\" | the name of the Otoroshi backoffice session |\n| `play.ws.play.ws.useragent` | string | \"Otoroshi\" | the user agent sent by Otoroshi if not present on the original http request |\n| `play.server.https.keyStore.path` | string | | the path to the keystore containing the private key and certificate, if not provided generates a keystore for you |\n| `play.server.https.keyStore.type` | string | JKS | the key store type, defaults to JKS |\n| `play.server.https.keyStore.password` | string | '' | the password, defaults to a blank password |\n| `play.server.https.keyStore.algorithm` | string | | the key store algorithm, defaults to the platforms default algorithm |\n\n## More config. options\n\nSee https://github.com/MAIF/otoroshi/blob/master/otoroshi/conf/base.conf and https://github.com/MAIF/otoroshi/blob/master/otoroshi/conf/application.conf\n\nif you want to configure https on your Otoroshi server, just read [PlayFramework documentation about it](https://www.playframework.com/documentation/2.6.x/ConfiguringHttps)\n\n## Example of configuration file\n\n```conf\ninclude \"application.conf\"\n\nhttp.port = 8080\n\napp {\n storage = \"leveldb\"\n importFrom = \"./my-state.json\"\n env = \"prod\"\n domain = \"oto.tools\"\n rootScheme = \"http\"\n snowflake {\n seed = 0\n }\n events {\n maxSize = 1000\n }\n backoffice {\n subdomain = \"otoroshi\"\n session {\n exp = 86400000\n }\n }\n privateapps {\n subdomain = \"privateapps\"\n session {\n exp = 86400000\n }\n }\n adminapi {\n targetSubdomain = \"otoroshi-admin-internal-api\"\n exposedSubdomain = \"otoroshi-api\"\n defaultValues {\n backOfficeGroupId = \"admin-api-group\"\n backOfficeApiKeyClientId = \"admin-api-apikey-id\"\n backOfficeApiKeyClientSecret = \"admin-api-apikey-secret\"\n backOfficeServiceId = \"admin-api-service\"\n }\n }\n claim {\n sharedKey = \"mysecret\"\n }\n leveldb {\n path = \"./leveldb\"\n }\n}\n\nplay.http {\n session {\n secure = false\n httpOnly = true\n maxAge = 2592000000\n domain = \".oto.tools\"\n cookieName = \"oto-sess\"\n }\n}\n```\n" }, { "name": "datastore.md", @@ -165,7 +165,7 @@ "id": "/getotoroshi/fromdocker.md", "url": "/getotoroshi/fromdocker.html", "title": "From docker", - "content": "# From docker\n\nIf you're a Docker aficionado, Otoroshi is provided as a Docker image that your can pull directly from Official repos.\n\nfirst, fetch the last Docker image of Otoroshi :\n\n```sh\ndocker pull maif/otoroshi:1.4.17\n# or \ndocker pull maif/otoroshi:latest\n# or \ndocker pull maif/otoroshi:jdk8-1.4.17\n# or \ndocker pull maif/otoroshi:jdk11-1.4.17\n# or \ndocker pull maif/otoroshi:jdk12-1.4.17\n# or \ndocker pull maif/otoroshi:jdk13-1.4.17\n# or \ndocker pull maif/otoroshi:jdk14-1.4.17\n```" + "content": "# From docker\n\nIf you're a Docker aficionado, Otoroshi is provided as a Docker image that your can pull directly from Official repos.\n\nfirst, fetch the last Docker image of Otoroshi :\n\n```sh\ndocker pull maif/otoroshi:1.4.18\n# or \ndocker pull maif/otoroshi:latest\n# or \ndocker pull maif/otoroshi:jdk8-1.4.18\n# or \ndocker pull maif/otoroshi:jdk11-1.4.18\n# or \ndocker pull maif/otoroshi:jdk12-1.4.18\n# or \ndocker pull maif/otoroshi:jdk13-1.4.18\n# or \ndocker pull maif/otoroshi:jdk14-1.4.18\n```" }, { "name": "fromsources.md", @@ -186,7 +186,7 @@ "id": "/index.md", "url": "/index.html", "title": "Otoroshi", - "content": "# Otoroshi\n\n**Otoroshi** is a layer of lightweight api management on top of a modern http reverse proxy written in Scala and developped by the MAIF OSS team that can handle all the calls to and between your microservices without service locator and let you change configuration dynamicaly at runtime.\n\n\n> *The Otoroshi is a large hairy monster that tends to lurk on the top of the torii gate in front of Shinto shrines. It's a hostile creature, but also said to be the guardian of the shrine and is said to leap down from the top of the gate to devour those who approach the shrine for only self-serving purposes.*\n\n@@@ div { .centered-img }\n[![Build Status](https://travis-ci.org/MAIF/otoroshi.svg?branch=master)](https://travis-ci.org/MAIF/otoroshi) [![Join the chat at https://gitter.im/MAIF/otoroshi](https://badges.gitter.im/MAIF/otoroshi.svg)](https://gitter.im/MAIF/otoroshi?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [ ![Download](https://img.shields.io/github/release/MAIF/otoroshi.svg) ](hhttps://github.com/MAIF/otoroshi/releases/download/v1.4.17/otoroshi.jar)\n@@@\n\n@@@ div { .centered-img }\n\n@@@\n\n## Installation\n\nYou can download the latest build of Otoroshi as a [fat jar](https://github.com/MAIF/otoroshi/releases/download/v1.4.17/otoroshi.jar), as a [zip package](https://github.com/MAIF/otoroshi/releases/download/v1.4.17/otoroshi-dist.zip) or as a @ref:[docker image](./getotoroshi/fromdocker.md).\n\nYou can install and run Otoroshi with this little bash snippet\n\n```sh\ncurl -L -o otoroshi.jar 'https://github.com/MAIF/otoroshi/releases/download/v1.4.17/otoroshi.jar'\njava -Dapp.domain=oto.tools -jar otoroshi.jar\n```\n\nor using docker\n\n```sh\ndocker run -p \"8080:8080\" -e \"APP_DOMAIN=oto.tools\" maif/otoroshi:1.4.17\n```\n\nnow open your browser to http://otoroshi.oto.tools:8080/, **log in with the credential generated in the logs** and explore by yourself, if you want better instructions, just go to the @ref:[Quick Start](./quickstart.md) or directly to the @ref:[installation instructions](./getotoroshi/index.md)\n\n## Documentation\n\n* @ref:[About Otoroshi](./about.md)\n* @ref:[Architecture](./archi.md)\n* @ref:[Features](./features.md)\n* @ref:[Try Otoroshi in 5 minutes](./quickstart.md)\n* @ref:[Video tutorials](./videos.md)\n* @ref:[Get Otoroshi](./getotoroshi/index.md)\n* @ref:[First run](./firstrun/index.md)\n* @ref:[Setup Otoroshi](./setup/index.md)\n* @ref:[Using Otoroshi](./usage/index.md)\n* @ref:[Third party Integrations](./integrations/index.md)\n* @ref:[Detailed topics](./topics/index.md)\n* @ref:[Embedding Otoroshi](./embedding.md)\n* @ref:[Admin REST API](./api.md)\n* @ref:[Rust CLI](./cli.md)\n* @ref:[Deploy to production](./deploy/index.md)\n* @ref:[Connectors](./connectors/index.md)\n\n## Discussion\n\nJoin the [Otoroshi](https://gitter.im/MAIF/otoroshi) channel on the [MAIF Gitter](https://gitter.im/MAIF)\n\n## Sources\n\nThe sources of Otoroshi are available on [Github](https://github.com/MAIF/otoroshi).\n\n## Logo\n\nYou can find the official Otoroshi logo [on GitHub](https://github.com/MAIF/otoroshi/blob/master/resources/otoroshi-logo.png). The Otoroshi logo has been created by François Galioto ([@fgalioto](https://twitter.com/fgalioto))\n\n## Changelog\n\nEvery release, along with the migration instructions, is documented on the [Github Releases](https://github.com/MAIF/otoroshi/releases) page.\n\n## Patrons\n\nThe work on Otoroshi was funded by MAIF with the help of the community.\n\n## Licence\n\nOtoroshi is Open Source and available under the [Apache 2 License](https://opensource.org/licenses/Apache-2.0)\n\n@@@ index\n\n* [About Otoroshi](about.md)\n* [Architecture](archi.md)\n* [Features](features.md)\n* [Quickstart](quickstart.md)\n* [Videos](videos.md)\n* [Get otoroshi](getotoroshi/index.md)\n* [First run](firstrun/index.md)\n* [Setup](setup/index.md)\n* [Using Otoroshi](usage/index.md)\n* [Integrations](integrations/index.md)\n* [Detailed topics](topics/index.md)\n* [Admin REST API](api.md)\n* [Embedding Otoroshi](./embedding.md)\n* [Official Rust CLI](cli.md)\n* [Deploy to production](deploy/index.md)\n* [Connectors](connectors/index.md)\n\n@@@\n" + "content": "# Otoroshi\n\n**Otoroshi** is a layer of lightweight api management on top of a modern http reverse proxy written in Scala and developped by the MAIF OSS team that can handle all the calls to and between your microservices without service locator and let you change configuration dynamicaly at runtime.\n\n\n> *The Otoroshi is a large hairy monster that tends to lurk on the top of the torii gate in front of Shinto shrines. It's a hostile creature, but also said to be the guardian of the shrine and is said to leap down from the top of the gate to devour those who approach the shrine for only self-serving purposes.*\n\n@@@ div { .centered-img }\n[![Build Status](https://travis-ci.org/MAIF/otoroshi.svg?branch=master)](https://travis-ci.org/MAIF/otoroshi) [![Join the chat at https://gitter.im/MAIF/otoroshi](https://badges.gitter.im/MAIF/otoroshi.svg)](https://gitter.im/MAIF/otoroshi?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [ ![Download](https://img.shields.io/github/release/MAIF/otoroshi.svg) ](hhttps://github.com/MAIF/otoroshi/releases/download/v1.4.18/otoroshi.jar)\n@@@\n\n@@@ div { .centered-img }\n\n@@@\n\n## Installation\n\nYou can download the latest build of Otoroshi as a [fat jar](https://github.com/MAIF/otoroshi/releases/download/v1.4.18/otoroshi.jar), as a [zip package](https://github.com/MAIF/otoroshi/releases/download/v1.4.18/otoroshi-dist.zip) or as a @ref:[docker image](./getotoroshi/fromdocker.md).\n\nYou can install and run Otoroshi with this little bash snippet\n\n```sh\ncurl -L -o otoroshi.jar 'https://github.com/MAIF/otoroshi/releases/download/v1.4.18/otoroshi.jar'\njava -jar otoroshi.jar\n```\n\nor using docker\n\n```sh\ndocker run -p \"8080:8080\" maif/otoroshi:1.4.18\n```\n\nnow open your browser to http://otoroshi.oto.tools:8080/, **log in with the credential generated in the logs** and explore by yourself, if you want better instructions, just go to the @ref:[Quick Start](./quickstart.md) or directly to the @ref:[installation instructions](./getotoroshi/index.md)\n\n## Documentation\n\n* @ref:[About Otoroshi](./about.md)\n* @ref:[Architecture](./archi.md)\n* @ref:[Features](./features.md)\n* @ref:[Try Otoroshi in 5 minutes](./quickstart.md)\n* @ref:[Video tutorials](./videos.md)\n* @ref:[Get Otoroshi](./getotoroshi/index.md)\n* @ref:[First run](./firstrun/index.md)\n* @ref:[Setup Otoroshi](./setup/index.md)\n* @ref:[Using Otoroshi](./usage/index.md)\n* @ref:[Third party Integrations](./integrations/index.md)\n* @ref:[Detailed topics](./topics/index.md)\n* @ref:[Embedding Otoroshi](./embedding.md)\n* @ref:[Admin REST API](./api.md)\n* @ref:[Rust CLI](./cli.md)\n* @ref:[Deploy to production](./deploy/index.md)\n* @ref:[Connectors](./connectors/index.md)\n\n## Discussion\n\nJoin the [Otoroshi](https://gitter.im/MAIF/otoroshi) channel on the [MAIF Gitter](https://gitter.im/MAIF)\n\n## Sources\n\nThe sources of Otoroshi are available on [Github](https://github.com/MAIF/otoroshi).\n\n## Logo\n\nYou can find the official Otoroshi logo [on GitHub](https://github.com/MAIF/otoroshi/blob/master/resources/otoroshi-logo.png). The Otoroshi logo has been created by François Galioto ([@fgalioto](https://twitter.com/fgalioto))\n\n## Changelog\n\nEvery release, along with the migration instructions, is documented on the [Github Releases](https://github.com/MAIF/otoroshi/releases) page.\n\n## Patrons\n\nThe work on Otoroshi was funded by MAIF with the help of the community.\n\n## Licence\n\nOtoroshi is Open Source and available under the [Apache 2 License](https://opensource.org/licenses/Apache-2.0)\n\n@@@ index\n\n* [About Otoroshi](about.md)\n* [Architecture](archi.md)\n* [Features](features.md)\n* [Quickstart](quickstart.md)\n* [Videos](videos.md)\n* [Get otoroshi](getotoroshi/index.md)\n* [First run](firstrun/index.md)\n* [Setup](setup/index.md)\n* [Using Otoroshi](usage/index.md)\n* [Integrations](integrations/index.md)\n* [Detailed topics](topics/index.md)\n* [Admin REST API](api.md)\n* [Embedding Otoroshi](./embedding.md)\n* [Official Rust CLI](cli.md)\n* [Deploy to production](deploy/index.md)\n* [Connectors](connectors/index.md)\n\n@@@\n" }, { "name": "analytics.md", @@ -235,7 +235,7 @@ "id": "/quickstart.md", "url": "/quickstart.html", "title": "Try Otoroshi in 5 minutes", - "content": "# Try Otoroshi in 5 minutes\n\nwhat you will need :\n\n* JDK 8\n* curl\n* 5 minutes of free time\n\nIf you don't/can't have these tools on your machine, you can start a sandboxed environment using here with the following command\n\n```sh\ndocker run -p \"8080:8080\" -it maif/otoroshi bash\n```\n\nor you can also try Otoroshi online with Google Cloud Shell if you're a user of the Google Cloud Platform\n\n@@@ div { .centered-img }\n[![Open in Cloud Shell](http://gstatic.com/cloudssh/images/open-btn.svg)](https://console.cloud.google.com/cloudshell/open?git_repo=https%3A%2F%2Fgithub.com%2Fmathieuancelin%2Fotoroshi-tutorial&page=shell&tutorial=tutorial.md)\n@@@\n\n## The elevator pitch\n\nOtoroshi is an awesome reverse proxy built with Scala that handles all the calls to and between your microservices without service locator and lets you change configuration dynamically at runtime.\n\n## I like sh but I really want to see the UI\n\nAs Otoroshi uses Otoroshi to serve its own admin UI and admin API, you won't be able to access the admin UI on `http://localhost:8080`. Otoroshi needs a domain name to know that you want to access the admin UI. By default, the admin UI is exposed on `http://otoroshi.oto.tools:8080`. Of course you can @ref:[configure](./firstrun/configfile.md#common-configuration) the domain of the subdomain. To configure access to the admin, just go to the [UI section of the quickstart](#what-about-the-ui).\n\n## Now some sh :)\n\n```sh\ncurl -L -o otoroshi.jar https://github.com/MAIF/otoroshi/releases/download/v1.4.17/otoroshi.jar\ncurl -L -o otoroshicli https://github.com/MAIF/otoroshi/releases/download/v1.4.17/linux-otoroshicli\n\nchmod +x otoroshicli\n\n# Run the Otoroshi server on Java 8\njava -jar otoroshi.jar &\n\n# Run the Otoroshi server on Java 9 and 10\njava --add-modules java.xml.bind -jar otoroshi.jar &\n\n# Check if admin api works\n./otoroshicli services all\n./otoroshicli apikeys all\n./otoroshicli groups all\n\n# Create a service that will proxy call to https://freegeoip.net through http://ip.geo.com:8080\n./otoroshicli services create --group default --name geo-ip-api --env prod \\\n --domain geo.com --subdomain ip --root /json/ --target https://freegeoip.net \\\n --public-pattern '/.*' --no-force-https\n\n# Then test it\n./otoroshicli tryout call \"http://127.0.0.1:8080/\" -X GET -H 'Host: ip.geo.com'\n# works with curl -X GET -H 'Host: ip.geo.com' \"http://127.0.0.1:8080/\" | jqn\n\n# Run 3 new microservices in 3 new terminal processes\n./otoroshicli tryout serve 9901\n./otoroshicli tryout serve 9902\n./otoroshicli tryout serve 9903\n\n# Create a service that will loadbalance between these 3 microservices and serves them through\n# http://api.hello.com:8080\n./otoroshicli services create --group default --id hello-api --name hello-api \\\n --env prod --domain hello.com --subdomain api --root / \\\n --target \"http://127.0.0.1:9901\" \\\n --target \"http://127.0.0.1:9902\" \\\n --public-pattern '/.*' --no-force-https --client-retries 3\n\n# Then test it multiple time to observe loadbalancing\n# You can also define '127.0.0.1 api.hello.com' in your /etc/hosts file and test it in your browser\n./otoroshicli tryout call \"http://127.0.0.1:8080/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\nsudo echo \"127.0.0.1 api.hello.com\" >> /etc/hosts\nopen \"http://api.hello.com:8080/\"\n\n# Then add a new target\n./otoroshicli services add-target hello-api --target=\"http://127.0.0.1:9903\"\n\n# Then test it multiple time to observe loadbalancing\n./otoroshicli tryout call \"http://127.0.0.1:8080/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\n\n# Then kill one of the microservices and test it multiple time to observe loadbalancing\n./otoroshicli tryout call \"http://127.0.0.1:8080/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\n\n# Then kill a second microservices and test it multiple time to observe loadbalancing\n./otoroshicli tryout call \"http://127.0.0.1:8080/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\n\n# Then kill the last microservices and test it to observe connection error\n./otoroshicli tryout call \"http://127.0.0.1:8080/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\n\n# Then delete your service\n./otoroshicli services delete hello-api\n```\n\n## What about the UI\n\nGo to http://otoroshi.oto.tools:8080/ and **log in with the credential generated in the logs** during first startup ;-)\n\nIf you want to know more about Otoroshi, you should continue reading the documentation starting with @ref:[how to get Otoroshi](./getotoroshi/index.md)\n\n## I don't have JDK 8 on my machine but I have Docker :)\n\nIf you want to use Docker, just follow these instructions\n\n```sh\n# here be careful, if you want to change the OTOROSHI_PORT, change the same value in the `otoroshicli.toml` config file\nexport OTOROSHI_PORT=8080\nexport LOCAL_IP_ADDRESS=999.999.999.999 # use your real local ip address here\n\ncurl -L -o otoroshicli https://github.com/MAIF/otoroshi/releases/download/v1.4.17/linux-otoroshicli\n\nchmod +x otoroshicli \n\ndocker run -p \"$OTOROSHI_PORT:8080\" maif/otoroshi &\n\n# Check if admin api works\n./otoroshicli services all\n./otoroshicli apikeys all\n./otoroshicli groups all\n\n# Create a service that will proxy call to https://freegeoip.net through http://ip.geo.com:$OTOROSHI_PORT\n./otoroshicli services create --group default --name geo-ip-api --env prod \\\n --domain geo.com --subdomain ip --root /json/ --target https://freegeoip.net \\\n --public-pattern '/.*' --no-force-https\n\n# Then test it\n./otoroshicli tryout call \"http://127.0.0.1:$OTOROSHI_PORT/\" -X GET -H 'Host: ip.geo.com'\n# works with curl -X GET -H 'Host: ip.geo.com' \"http://127.0.0.1:$OTOROSHI_PORT/\" | jqn\n\n# Run 3 new microservices in 3 new terminal processes\n./otoroshicli tryout serve 9901\n./otoroshicli tryout serve 9902\n./otoroshicli tryout serve 9903\n\n# Create a service that will loadbalance between these 3 microservices and serves them through\n# http://api.hello.com:$OTOROSHI_PORT\n./otoroshicli services create --group default --id hello-api --name hello-api \\\n --env prod --domain hello.com --subdomain api --root / \\\n --target \"http://$LOCAL_IP_ADDRESS:9901\" \\\n --target \"http://$LOCAL_IP_ADDRESS:9902\" \\\n --public-pattern '/.*' --no-force-https --client-retries 3\n\n# Then test it multiple time to observe loadbalancing\n# You can also define '127.0.0.1 api.hello.com' in your /etc/hosts file and test it in your browser\n./otoroshicli tryout call \"http://127.0.0.1:$OTOROSHI_PORT/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\nsudo echo \"127.0.0.1 api.hello.com\" >> /etc/hosts\nopen \"http://api.hello.com:$OTOROSHI_PORT/\"\n\n# Then add a new target\n./otoroshicli services add-target hello-api --target=\"http://$LOCAL_IP_ADDRESS:9903\"\n\n# Then test it multiple time to observe loadbalancing\n./otoroshicli tryout call \"http://127.0.0.1:$OTOROSHI_PORT/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\n\n# Then kill one of the microservices and test it multiple time to observe loadbalancing\n./otoroshicli tryout call \"http://127.0.0.1:$OTOROSHI_PORT/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\n\n# Then kill a second microservices and test it multiple time to observe loadbalancing\n./otoroshicli tryout call \"http://127.0.0.1:$OTOROSHI_PORT/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\n\n# Then kill the last microservices and test it to observe connection error\n./otoroshicli tryout call \"http://127.0.0.1:$OTOROSHI_PORT/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\n\n# Then delete your service\n./otoroshicli services delete hello-api\n```\n" + "content": "# Try Otoroshi in 5 minutes\n\nwhat you will need :\n\n* JDK 8\n* curl\n* 5 minutes of free time\n\nIf you don't/can't have these tools on your machine, you can start a sandboxed environment using here with the following command\n\n```sh\ndocker run -p \"8080:8080\" -it maif/otoroshi bash\n```\n\nor you can also try Otoroshi online with Google Cloud Shell if you're a user of the Google Cloud Platform\n\n@@@ div { .centered-img }\n[![Open in Cloud Shell](http://gstatic.com/cloudssh/images/open-btn.svg)](https://console.cloud.google.com/cloudshell/open?git_repo=https%3A%2F%2Fgithub.com%2Fmathieuancelin%2Fotoroshi-tutorial&page=shell&tutorial=tutorial.md)\n@@@\n\n## The elevator pitch\n\nOtoroshi is an awesome reverse proxy built with Scala that handles all the calls to and between your microservices without service locator and lets you change configuration dynamically at runtime.\n\n## I like sh but I really want to see the UI\n\nAs Otoroshi uses Otoroshi to serve its own admin UI and admin API, you won't be able to access the admin UI on `http://localhost:8080`. Otoroshi needs a domain name to know that you want to access the admin UI. By default, the admin UI is exposed on `http://otoroshi.oto.tools:8080`. Of course you can @ref:[configure](./firstrun/configfile.md#common-configuration) the domain of the subdomain. To configure access to the admin, just go to the [UI section of the quickstart](#what-about-the-ui).\n\n## Now some sh :)\n\n```sh\ncurl -L -o otoroshi.jar https://github.com/MAIF/otoroshi/releases/download/v1.4.18/otoroshi.jar\ncurl -L -o otoroshicli https://github.com/MAIF/otoroshi/releases/download/v1.4.18/linux-otoroshicli\n\nchmod +x otoroshicli\n\n# Run the Otoroshi server on Java 8\njava -jar otoroshi.jar &\n\n# Run the Otoroshi server on Java 9 and 10\njava --add-modules java.xml.bind -jar otoroshi.jar &\n\n# Check if admin api works\n./otoroshicli services all\n./otoroshicli apikeys all\n./otoroshicli groups all\n\n# Create a service that will proxy call to https://freegeoip.net through http://ip.geo.com:8080\n./otoroshicli services create --group default --name geo-ip-api --env prod \\\n --domain geo.com --subdomain ip --root /json/ --target https://freegeoip.net \\\n --public-pattern '/.*' --no-force-https\n\n# Then test it\n./otoroshicli tryout call \"http://127.0.0.1:8080/\" -X GET -H 'Host: ip.geo.com'\n# works with curl -X GET -H 'Host: ip.geo.com' \"http://127.0.0.1:8080/\" | jqn\n\n# Run 3 new microservices in 3 new terminal processes\n./otoroshicli tryout serve 9901\n./otoroshicli tryout serve 9902\n./otoroshicli tryout serve 9903\n\n# Create a service that will loadbalance between these 3 microservices and serves them through\n# http://api.hello.com:8080\n./otoroshicli services create --group default --id hello-api --name hello-api \\\n --env prod --domain hello.com --subdomain api --root / \\\n --target \"http://127.0.0.1:9901\" \\\n --target \"http://127.0.0.1:9902\" \\\n --public-pattern '/.*' --no-force-https --client-retries 3\n\n# Then test it multiple time to observe loadbalancing\n# You can also define '127.0.0.1 api.hello.com' in your /etc/hosts file and test it in your browser\n./otoroshicli tryout call \"http://127.0.0.1:8080/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\nsudo echo \"127.0.0.1 api.hello.com\" >> /etc/hosts\nopen \"http://api.hello.com:8080/\"\n\n# Then add a new target\n./otoroshicli services add-target hello-api --target=\"http://127.0.0.1:9903\"\n\n# Then test it multiple time to observe loadbalancing\n./otoroshicli tryout call \"http://127.0.0.1:8080/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\n\n# Then kill one of the microservices and test it multiple time to observe loadbalancing\n./otoroshicli tryout call \"http://127.0.0.1:8080/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\n\n# Then kill a second microservices and test it multiple time to observe loadbalancing\n./otoroshicli tryout call \"http://127.0.0.1:8080/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\n\n# Then kill the last microservices and test it to observe connection error\n./otoroshicli tryout call \"http://127.0.0.1:8080/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\n\n# Then delete your service\n./otoroshicli services delete hello-api\n```\n\n## What about the UI\n\nGo to http://otoroshi.oto.tools:8080/ and **log in with the credential generated in the logs** during first startup ;-)\n\nIf you want to know more about Otoroshi, you should continue reading the documentation starting with @ref:[how to get Otoroshi](./getotoroshi/index.md)\n\n## I don't have JDK 8 on my machine but I have Docker :)\n\nIf you want to use Docker, just follow these instructions\n\n```sh\n# here be careful, if you want to change the OTOROSHI_PORT, change the same value in the `otoroshicli.toml` config file\nexport OTOROSHI_PORT=8080\nexport LOCAL_IP_ADDRESS=999.999.999.999 # use your real local ip address here\n\ncurl -L -o otoroshicli https://github.com/MAIF/otoroshi/releases/download/v1.4.18/linux-otoroshicli\n\nchmod +x otoroshicli \n\ndocker run -p \"$OTOROSHI_PORT:8080\" maif/otoroshi &\n\n# Check if admin api works\n./otoroshicli services all\n./otoroshicli apikeys all\n./otoroshicli groups all\n\n# Create a service that will proxy call to https://freegeoip.net through http://ip.geo.com:$OTOROSHI_PORT\n./otoroshicli services create --group default --name geo-ip-api --env prod \\\n --domain geo.com --subdomain ip --root /json/ --target https://freegeoip.net \\\n --public-pattern '/.*' --no-force-https\n\n# Then test it\n./otoroshicli tryout call \"http://127.0.0.1:$OTOROSHI_PORT/\" -X GET -H 'Host: ip.geo.com'\n# works with curl -X GET -H 'Host: ip.geo.com' \"http://127.0.0.1:$OTOROSHI_PORT/\" | jqn\n\n# Run 3 new microservices in 3 new terminal processes\n./otoroshicli tryout serve 9901\n./otoroshicli tryout serve 9902\n./otoroshicli tryout serve 9903\n\n# Create a service that will loadbalance between these 3 microservices and serves them through\n# http://api.hello.com:$OTOROSHI_PORT\n./otoroshicli services create --group default --id hello-api --name hello-api \\\n --env prod --domain hello.com --subdomain api --root / \\\n --target \"http://$LOCAL_IP_ADDRESS:9901\" \\\n --target \"http://$LOCAL_IP_ADDRESS:9902\" \\\n --public-pattern '/.*' --no-force-https --client-retries 3\n\n# Then test it multiple time to observe loadbalancing\n# You can also define '127.0.0.1 api.hello.com' in your /etc/hosts file and test it in your browser\n./otoroshicli tryout call \"http://127.0.0.1:$OTOROSHI_PORT/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\nsudo echo \"127.0.0.1 api.hello.com\" >> /etc/hosts\nopen \"http://api.hello.com:$OTOROSHI_PORT/\"\n\n# Then add a new target\n./otoroshicli services add-target hello-api --target=\"http://$LOCAL_IP_ADDRESS:9903\"\n\n# Then test it multiple time to observe loadbalancing\n./otoroshicli tryout call \"http://127.0.0.1:$OTOROSHI_PORT/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\n\n# Then kill one of the microservices and test it multiple time to observe loadbalancing\n./otoroshicli tryout call \"http://127.0.0.1:$OTOROSHI_PORT/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\n\n# Then kill a second microservices and test it multiple time to observe loadbalancing\n./otoroshicli tryout call \"http://127.0.0.1:$OTOROSHI_PORT/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\n\n# Then kill the last microservices and test it to observe connection error\n./otoroshicli tryout call \"http://127.0.0.1:$OTOROSHI_PORT/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\n\n# Then delete your service\n./otoroshicli services delete hello-api\n```\n" }, { "name": "admin.md", diff --git a/manual/src/main/paradox/content.json b/manual/src/main/paradox/content.json index aa362424d9..58f26faab4 100644 --- a/manual/src/main/paradox/content.json +++ b/manual/src/main/paradox/content.json @@ -1 +1 @@ -[{"name":"about.md","id":"/about.md","url":"/about.html","title":"About Otoroshi","content":"# About Otoroshi\n\nAt the beginning of 2017, we had the need to create a new environment to be able to create new \"digital\" products very quickly in an agile fashion at MAIF. Naturally we turned to PaaS solutions and chose the excellent Clever-Cloud product to run our apps. \n\nWe also chose that every feature team will have the freedom to choose its own technological stack to build its product. It was a nice move but it has also introduced some challenges in terms of homogeneity for traceability, security, logging, ... because we did not want to force library usage in the products. We could have used something like Service Mesh Pattern but the deployement model of Clever-Cloud prevented us to do it.\n\nThe right solution was to use a reverse proxy or some kind of API Gateway able to provide tracability, logging, security with apikeys, quotas, DNS as a service locator, etc. We needed something easy to use, with a human friendly UI, a nice API to extends its features, true hot reconfiguration, able to generate internal events for third party usage. A couple of solutions were available at that time, but not one seems to fit our needs, there was always something missing, too complicated for our needs or not playing well with Clever-Cloud deployment model.\n\nAt some point, we tried to write a small prototype to explore what could be our dream reverse proxy. The design was very simple, there were some rough edges but every major feature needed was there waiting to be enhanced.\n\n**Otoroshi** was born and we decided to move ahead with our hairy monster :)\n\n## Philosophy \n\nEvery OSS product build at MAIF like Izanami follow a common philosophy. \n\n* the services or API provided should be technology agnostic.\n* http first: http is the right answer to the previous quote \n* api First: The UI is just another client of the api. \n* secured: The services exposed need authentication for both humans or machines \n* event based: The services should expose a way to get notified of what happened inside. \n"},{"name":"api.md","id":"/api.md","url":"/api.html","title":"Admin REST API","content":"# Admin REST API\n\nOtoroshi provides a fully featured REST admin API to perform almost every operation possible in the Otoroshi dashboard. The Otoroshi dashbaord is just a regular consumer of the admin API.\n\nUsing the admin API, you can do whatever you want and enhance your Otoroshi instances with a lot of features that will feet your needs.\n\nOtoroshi also provides some connectors that uses the Otoroshi admin API to automate Otorshi's instances when used with stuff like containers orchestrators. For more informations about that, just go to the @ref:[third party integrations chapter](./integrations/index.md)\n\n## Swagger descriptor\n\nThe Otoroshi admin API is described using OpenAPI format and is available at :\n\nhttps://maif.github.io/otoroshi/manual/code/swagger.json\n\nEvery Otoroshi instance provides its own embedded OpenAPI descriptor at :\n\nhttp://otoroshi.oto.tools:8080/api/swagger.json\n\n## Swagger documentation\n\nYou can read the OpenAPI descriptor in a more human friendly fashion using `Swagger UI`. The swagger UI documentation of the Otoroshi admin API is available at :\n\nhttps://maif.github.io/otoroshi/swagger-ui/index.html\n\nEvery Otoroshi instance provides its own embedded OpenAPI descriptor at :\n\nhttp://otoroshi.oto.tools:8080/api/swagger/ui\n\nYou can also read the swagger UI documentation of the Otoroshi admin API below :\n\n@@@ div { .swagger-frame }\n\n\n@@@\n"},{"name":"archi.md","id":"/archi.md","url":"/archi.html","title":"Architecture","content":"# Architecture\n\nWhen we started the development of Otoroshi, we had several classical patterns in mind like `Service gateway`, `Service locator`, `Circuit breakers`, etc ...\n\nAt start we thought about providing a bunch of librairies that would be included in each microservice or app to perform these tasks. But the more we were thinking about it, the more it was feeling weird, unagile, etc, it also prevented us to use any technical stack we wanted to use. So we decided to change our approach to something more universal.\n\nWe chose to make Otoroshi the central part of our microservices system, something between a reverse-proxy, a service gateway and a service locator where each call to a microservice (even from another microservice) must pass through Otoroshi. There are multiple benefits to do that, each call can be logged, audited, monitored, integrated with a circuit breaker, etc without imposing libraries and technical stack. Any service is exposed through its own domain and we rely only on DNS to handle the service location part. Any access to a service is secured by default with an api key and is supervised by a circuit breaker to avoid cascading failures.\n\n@@@ div { .centered-img }\n\n@@@\n\nOtoroshi tries to embrace our @ref:[global philosophy](./about.md#philosophy) by providing a full featured REST admin api, a gorgeous admin dashboard written in [React](https://reactjs.org/) that uses the api, by generating traffic events, alerts events, audit events that can be consumed by several channels. Otoroshi also supports a bunch of datastores to better match with different use cases.\n\n@@@ div { .centered-img }\n\n@@@\n"},{"name":"cli.md","id":"/cli.md","url":"/cli.html","title":"Rust CLI","content":"# Rust CLI\n\n@@@ warning\nOtoroshi had a CLI written in Rust until v1.4.2 but it's deprecated now. If you are interested in maintaining this tool, just tell us ;)\n@@@\n"},{"name":"clevercloud.md","id":"/connectors/clevercloud.md","url":"/connectors/clevercloud.html","title":"Clever Cloud *","content":"# Clever Cloud *\n\n@@@ warning\nThis is an experimental connector\n@@@\n\nThe documentation is not ready yet as it's an experimental connector, but you can read more about it [here](https://github.com/MAIF/otoroshi/tree/master/connectors/clevercloud).\n"},{"name":"index.md","id":"/connectors/index.md","url":"/connectors/index.html","title":"Connectors","content":"# Connectors\n\nOtoroshi provides a set of connectors to be used in some use cases that could be useful :\n\n* [rancher connector](https://github.com/MAIF/otoroshi/tree/master/connectors/rancher) : a connector to synchronize a rancher cluster with Otoroshi on the fly\n* [kubernetes connector](https://github.com/MAIF/otoroshi/tree/master/connectors/kubernetes) : a connector to synchronize a kubernetes cluster with Otoroshi on the fly\n* [Clever-Cloud connector](https://github.com/MAIF/otoroshi/tree/master/connectors/clevercloud) : a connector to synchronize a Clever-Cloud organization with Otoroshi on the fly\n\nIf you want to build your own connector, Otoroshi provides a common library for synchronization. You can find the code here :\n\nhttps://github.com/MAIF/otoroshi/tree/master/connectors/common\n\n@@@ index\n\n* [clevercloud](./clevercloud.md)\n* [kubernetes](./kubernetes.md)\n* [rancher](./rancher.md)\n\n@@@\n"},{"name":"kubernetes.md","id":"/connectors/kubernetes.md","url":"/connectors/kubernetes.html","title":"Kubernetes *","content":"# Kubernetes *\n\n@@@ warning\nThis is an experimental connector\n@@@\n\nThe documentation is not ready yet as it's an experimental connector, but you can read more about it [here](https://github.com/MAIF/otoroshi/tree/master/connectors/kubernetes).\n"},{"name":"rancher.md","id":"/connectors/rancher.md","url":"/connectors/rancher.html","title":"Rancher *","content":"# Rancher *\n\n@@@ warning\nThis is an experimental connector\n@@@\n\nThe documentation is not ready yet as it's an experimental connector, but you can read more about it [here](https://github.com/MAIF/otoroshi/tree/master/connectors/rancher).\n"},{"name":"aws-beanstalk.md","id":"/deploy/aws-beanstalk.md","url":"/deploy/aws-beanstalk.html","title":"AWS - Elastic Beanstalk","content":"# AWS - Elastic Beanstalk\n\nNow you want to use Otoroshi on AWS. There are multiple options to deploy Otoroshi on AWS, \nfor instance :\n\n* You can deploy the [Docker image](../getotoroshi/fromdocker.md) on [Amazon ECS](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html)\n* You can create a basic [Amazon EC2](https://docs.aws.amazon.com/fr_fr/AWSEC2/latest/UserGuide/concepts.html), access it via SSH, then \ndeploy the @ref:[otoroshi.jar](../firstrun/run.md#from-jar-file) \n* Or you can use [AWS Elastic Beanstalk](https://aws.amazon.com/fr/elasticbeanstalk)\n\nIn this section we are going to cover how to deploy Otoroshi on [AWS Elastic Beanstalk](https://aws.amazon.com/fr/elasticbeanstalk). \n\n## AWS Elastic Beanstalk Overview\nUnlike Clever Cloud, to deploy an application on AWS Elastic Beanstalk, you don't link your app to your VCS repository, push your code and expect it to be built and run.\n\nAWS Elastic Beanstalk does only the run part. So you have to handle your own build pipeline, upload a Zip file containing your runnable, then AWS Elastic Beanstalk will take it from there. \n \nEg: for apps running on the JVM (Scala/Java/Kotlin) a Zip with the jar inside would suffice, for apps running in a Docker container, a Zip with the DockerFile would be enough. \n\n\n## Prepare your deployment target\nActually, there are 2 options to build your target. \n\nEither you create a DockerFile from this [Docker image](../getotoroshi/fromdocker.md), build a zip, and do all the Otoroshi custom configuration using ENVs.\n\nOr you download the @ref:[otoroshi.jar](../getotoroshi/frombinaries.md), do all the Otoroshi custom configuration using your own otoroshi.conf, and create a DockerFile that runs the jar using your otoroshi.conf. \n\nFor the second option your DockerFile would look like this :\n\n```dockerfile\nFROM openjdk:8\nVOLUME /tmp\nEXPOSE 8080\nADD otoroshi.jar otoroshi.jar\nADD otoroshi.conf otoroshi.conf\nRUN sh -c 'touch /otoroshi.jar'\nENV JAVA_OPTS=\"\"\nENTRYPOINT [ \"sh\", \"-c\", \"java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -Dconfig.file=/otoroshi.conf -jar /otoroshi.jar\" ]\n``` \n \nI'd recommend the second option.\n \nNow Zip your target (Jar + Conf + DockerFile) and get ready for deployment. \n\n## Create an Otoroshi instance on AWS Elastic Beanstalk\nFirst, go to [AWS Elastic Beanstalk Console](https://eu-west-3.console.aws.amazon.com/elasticbeanstalk/home?region=eu-west-3#/welcome), don't forget to sign in and make sure that you are in the good region (eg : eu-west-3 for Paris).\n\nHit **Get started** \n\n@@@ div { .centered-img }\n\n@@@\n\nSpecify the **Application name** of your application, Otoroshi for example.\n\n@@@ div { .centered-img }\n\n@@@\n \nChoose the **Platform** of the application you want to create, in your case use Docker.\n\nFor **Application code** choose **Upload your code** then hit **Upload**.\n\n@@@ div { .centered-img }\n\n@@@\n\nBrowse the zip created in the [previous section](#prepare-your-deployment-target) from your machine. \n\nAs you can see in the image above, you can also choose an S3 location, you can imagine that at the end of your build pipeline you upload your Zip to S3, and then get it from there (I wouldn't recommend that though).\n \nWhen the upload is done, hit **Configure more options**.\n \n@@@ div { .centered-img }\n\n@@@ \n \nRight now an AWS Elastic Beanstalk application has been created, and by default an environment named Otoroshi-env is being created as well.\n\nAWS Elastic Beanstalk can manage multiple environments of the same application, for instance environments can be (prod, preprod, expriments...). \n\nOtoroshi is a bit particular, it doesn't make much sense to have multiple environments, since Otoroshi will handle all the requests from/to downstream services regardless of the environment. \n \nAs you see in the image above, we are now configuring the Otoroshi-env, the one and only environment of Otoroshi.\n \nFor **Configuration presets**, choose custom configuration, now you have a load balancer for your environment with the capacity of at least one instance and at most four.\nI'd recommend at least 2 instances, to change that, on the **Capacity** card hit **Modify**. \n\n@@@ div { .centered-img }\n\n@@@\n\nChange the **Instances** to min 2, max 4 then hit **Save**. For the **Scaling triggers**, I'd keep the default values, but know that you can edit the capacity config any time you want, it only costs a redeploy, which will be done automatically by the way.\n \nInstances size is by default t2.micro, which is a bit small for running Otoroshi, I'd recommend a t2.medium. \nOn the **Instances** card hit **Modify**.\n\n@@@ div { .centered-img }\n\n@@@\n\nFor **Instance type** choose t2.medium, then hit **Save**, no need to change the volume size, unless you have a lot of http call faults, which means a lot more logs, in that case the default volume size may not be enough.\n\nThe default environment created for Otoroshi, for instance Otoroshi-env, is a web server environment which fits in your case, but the thing is that on AWS Elastic Beanstalk by default a web server environment for a docker-based application, runs behind an Nginx proxy.\nWe have to remove that proxy. So on the **Software** card hit **Modify**.\n \n@@@ div { .centered-img }\n\n@@@ \n \nFor **Proxy server** choose None then hit **Save**.\n\nAlso note that you can set Envs for Otoroshi in same page (see image below). \n\n@@@ div { .centered-img }\n\n@@@ \n\nTo finalise the creation process, hit **Create app** on the bottom right.\n\nThe Otoroshi app is now created, and it's running which is cool, but we still don't have neither a **datastore** nor **https**.\n \n## Create an Otoroshi datastore on AWS ElastiCache\n\nBy default Otoroshi uses non persistent memory to store it's data, Otoroshi supports many kinds of datastores. In this section we will be covering Redis datastore. \n\nBefore starting, using a datastore hosted by AWS is not at all mandatory, feel free to use your own if you like, but if you want to learn more about ElastiCache, this section may interest you, otherwise you can skip it.\n\nGo to [AWS ElastiCache](https://eu-west-3.console.aws.amazon.com/elasticache/home?region=eu-west-3#) and hit **Get Started Now**.\n\n@@@ div { .centered-img }\n\n@@@ \n\nFor **Cluster engine** keep Redis.\n\nChoose a **Name** for your datastore, for instance otoroshi-datastore.\n\nYou can keep all the other default values and hit **Create** on the bottom right of the page.\n\nOnce your Redis Cluster is created, it would look like the image below.\n\n@@@ div { .centered-img }\n\n@@@ \n\n\nFor applications in the same security group as your cluster, redis cluster is accessible via the **Primary Endpoint**. Don't worry the default security group is fine, you don't need any configuration to access the cluster from Otoroshi.\n\nTo make Otoroshi use the created cluster, you can either use Envs `APP_STORAGE=redis`, `REDIS_HOST` and `REDIS_PORT`, or set `app.storage=redis`, `app.redis.host` and `app.redis.port` in your otoroshi.conf.\n\n## Create SSL certificate and configure your domain\n\nOtoroshi has now a datastore, but not yet ready for use. \n\nIn order to get it ready you need to :\n\n* Configure Otoroshi with your domain \n* Create a wildcard SSL certificate for your domain\n* Configure Otoroshi AWS Elastic Beanstalk instance with the SSL certificate \n* Configure your DNS to redirect all traffic on your domain to Otoroshi \n \n### Configure Otoroshi with your domain\n\nYou can use ENVs or you can use a custom otoroshi.conf in your Docker container.\n\nFor the second option your otoroshi.conf would look like this :\n\n``` \n include \"application.conf\"\n http.port = 8080\n app {\n env = \"prod\"\n domain = \"mysubdomain.oto.tools\"\n rootScheme = \"https\"\n snowflake {\n seed = 0\n }\n events {\n maxSize = 1000\n }\n backoffice {\n subdomain = \"otoroshi\"\n session {\n exp = 86400000\n }\n }\n \n storage = \"redis\"\n redis {\n host=\"myredishost\"\n port=myredisport\n }\n \n privateapps {\n subdomain = \"privateapps\"\n }\n \n adminapi {\n targetSubdomain = \"otoroshi-admin-internal-api\"\n exposedSubdomain = \"otoroshi-api\"\n defaultValues {\n backOfficeGroupId = \"admin-api-group\"\n backOfficeApiKeyClientId = \"admin-client-id\"\n backOfficeApiKeyClientSecret = \"admin-client-secret\"\n backOfficeServiceId = \"admin-api-service\"\n }\n proxy {\n https = true\n local = false\n }\n }\n claim {\n sharedKey = \"myclaimsharedkey\"\n }\n }\n \n play.http {\n session {\n secure = false\n httpOnly = true\n maxAge = 2147483646\n domain = \".mysubdomain.oto.tools\"\n cookieName = \"oto-sess\"\n }\n }\n``` \n\n### Create a wildcard SSL certificate for your domain\n\nGo to [AWS Certificate Manager](https://eu-west-3.console.aws.amazon.com/acm/home?region=eu-west-3#/firstrun).\n\nBelow **Provision certificates** hit **Get started**.\n\n@@@ div { .centered-img }\n\n@@@ \n \nKeep the default selected value **Request a public certificate** and hit **Request a certificate**.\n \n@@@ div { .centered-img }\n\n@@@ \n\nPut your **Domain name**, use *. for wildcard, for instance *\\*.mysubdomain.oto.tools*, then hit **Next**.\n\n@@@ div { .centered-img }\n\n@@@ \n\nYou can choose between **Email validation** and **DNS validation**, I'd recommend **DNS validation**, then hit **Review**. \n \n@@@ div { .centered-img }\n\n@@@ \n \nVerify that you did put the right **Domain name** then hit **Confirm and request**. \n\n@@@ div { .centered-img }\n\n@@@\n \nAs you see in the image above, to let Amazon do the validation you have to add the `CNAME` record to your DNS configuration. Normally this operation takes around one day.\n \n### Configure Otoroshi AWS Elastic Beanstalk instance with the SSL certificate \n\nOnce the certificate is validated, you need to modify the configuration of Otoroshi-env to add the SSL certificate for HTTPS. \nFor that you need to go to [AWS Elastic Beanstalk applications](https://eu-west-3.console.aws.amazon.com/elasticbeanstalk/home?region=eu-west-3#/applications),\nhit **Otoroshi-env**, then on the left side hit **Configuration**, then on the **Load balancer** card hit **Modify**.\n\n@@@ div { .centered-img }\n\n@@@\n\nIn the **Application Load Balancer** section hit **Add listener**.\n\n@@@ div { .centered-img }\n\n@@@\n\nFill the popup as the image above, then hit **Add**. \n\nYou should now be seeing something like this : \n \n@@@ div { .centered-img }\n\n@@@ \n \n \nMake sure that your listener is enabled, and on the bottom right of the page hit **Apply**.\n\nNow you have **https**, so let's use Otoroshi.\n\n### Configure your DNS to redirect all traffic on your domain to Otoroshi\n \nIt's actually pretty simple, you just need to add a `CNAME` record to your DNS configuration, that redirects *\\*.mysubdomain.oto.tools* to the DNS name of Otoroshi's load balancer.\n\nTo find the DNS name of Otoroshi's load balancer go to [AWS Ec2](https://eu-west-3.console.aws.amazon.com/ec2/v2/home?region=eu-west-3#LoadBalancers:tag:elasticbeanstalk:environment-name=Otoroshi-env;sort=loadBalancerName)\n\nYou would find something like this : \n \n@@@ div { .centered-img }\n\n@@@ \n\nThere is your DNS name, so add your `CNAME` record. \n \nOnce all these steps are done, the AWS Elastic Beanstalk Otoroshi instance, would now be handling all the requests on your domain. ;) \n"},{"name":"clevercloud.md","id":"/deploy/clevercloud.md","url":"/deploy/clevercloud.html","title":"Clever Cloud","content":"# Clever Cloud\n\nNow you want to use Otoroshi on Clever Cloud. Otoroshi has been designed and created to run on Clever Cloud and a lot of choices were made because of how Clever Cloud works.\n\n## Create an Otoroshi instance on CleverCloud\n\nFirst, fork our project template on Github at https://github.com/MAIF/otoroshi-jar-clevercloud-template.\n\nIf you want to customize the build script, edit `./clevercloud/build.sh`\n\nIf you want to customize the configuration @ref:[use env. variables](../firstrun/env.md), you can use [the example provided below](#example-of-clevercloud-env-variables)\n\nCreate a new CleverCloud app based on your fork.\n\n@@@ div { .centered-img }\n\n@@@\n\nThen choose what kind of app your want to create, for Otoroshi, choose `Java + Jar`\n\n@@@ div { .centered-img }\n\n@@@\n\nNext, set up choose instance size and auto-scalling. Otoroshi can run on small instances, especially if you just want to test it.\n\n@@@ div { .centered-img }\n\n@@@\n\nFinally, choose a name for your app\n\n@@@ div { .centered-img }\n\n@@@\n\nNow you just need to customize environnment variables and add the custom build script as pre buid hook :\n\n`CC_PRE_BUILD_HOOK=./clevercloud/build.sh`\n\nat this point, you can also add other env. variables to configure Otoroshi like in [the example provided below](#example-of-clevercloud-env-variables)\n\n@@@ div { .centered-img }\n\n@@@\n\nYou can also use expert mode :\n\n@@@ div { .centered-img }\n\n@@@\n\nNow, your app is ready, don't forget to add a custom domains name on the CleverCloud app matching the Otoroshi app domain. For instance if you used domain names in env. variables like `changeme`, `changeme-admin-internal-api`, `changeme-api` on the `cleverapps.io` domain, declare `changeme.cleverapps.io`, `changeme-api.cleverapps.io`, `changeme-admin-internal-api.cleverapps.io`.\n\nYou will find the login/password tuple for first login in the app. logs.\n\n## Build and deploy Otoroshi from its source code\n\nFirst, fork our project template on Github at https://github.com/MAIF/otoroshi-clevercloud-template.\n\nIf you want to customize the build script, edit `./clevercloud/build.sh`\n\nIf you want to customize the configuration file, edit `./clevercloud/prod.conf` or @ref:[use env. variables](../firstrun/env.md)\n\nCreate a new Clever Cloud app based on your fork.\n\n@@@ div { .centered-img }\n\n@@@\n\nThen, you need to choose what kind of app your want to create, for Otoroshi, choose `Java or Scala + Play 2`\n\n@@@ div { .centered-img }\n\n@@@\n\nThen, you will be asked to choose what kind of machine you want to use. `M` instances are a good choice but you can use a less powerful ones. You can also activate auto-scaling or multi-instances to provie high availibility.\n\n@@@ div { .centered-img }\n\n@@@\n\nThen choose a name for your app :\n\n@@@ div { .centered-img }\n\n@@@\n\nNow you just need to customize environnment variables and add the custom build script as pre build hook :\n\n`CC_PRE_BUILD_HOOK=./clevercloud/build.sh`\n\nat this point, you can also add other env. variables to configure Otoroshi like in [the example provided below](#example-of-clevercloud-env-variables)\n\n@@@ div { .centered-img }\n\n@@@\n\nYou can also use expert mode :\n\n@@@ div { .centered-img }\n\n@@@\n\nNow, your app is ready, don't forget to add a custom domains name on the CleverCloud app matching the Otoroshi app domain. For instance if you used domain names in env. variables like `changeme`, `changeme-admin-internal-api`, `changeme-api` on the `cleverapps.io` domain, declare `changeme.cleverapps.io`, `changeme-api.cleverapps.io`, `changeme-admin-internal-api.cleverapps.io`.\n\nYou will find the login/password tuple for first login in the app. logs.\n\n## Example of CleverCloud env. variables\n\nYou can add more env variables to customize your Otoroshi instance like the following. Use the expert mode to copy/paste all the values in one shot :\n\n```\nAPP_ENV=prod\nAPP_STORAGE=inmemory\nAPP_DOMAIN=cleverapps.io\nAPP_ROOT_SCHEME=https\nAPP_BACKOFFICE_SUBDOMAIN=changeme\nADMIN_API_TARGET_SUBDOMAIN=changeme-admin-internal-api\nADMIN_API_EXPOSED_SUBDOMAIN=changeme-api\nADMIN_API_GROUP=psIZ0hI6eAQ2vp7DQoFfdUSfdmamtlkbXwYCe9WQHGBZMO6o5Kn1r2VVSmI61IVX\nADMIN_API_CLIENT_ID=pWkwudAifrflg8Bh\nADMIN_API_CLIENT_SECRET=ip53UuY5BFiM3wXkVUhhYrVdbsDYsANCNdRMnW3pU4I268ylsF6xxkvusS6Wv4AW\nADMIN_API_SERVICE_ID=GVQUWMZHaEYr1tCTNe9CdXOVE4DQnu1VUAx7YyXDlo5XupY3laZlWUnGyDt1vfGx\nCACHE_DEPENDENCIES=true\nCC_PRE_BUILD_HOOK=./clevercloud/build.sh\nCLAIM_SHAREDKEY=Tx1uQXW11pLNlZ25S4A08Uf8HbWDPxZ3KGSSm0B1s90gRk10PNy4d1HKY4Dnvvv5\nENABLE_METRICS=true\nJAVA_VERSION=8\nPORT=8080\nPLAY_CRYPTO_SECRET=7rNFga4AComd6ey09W9PaHqllLmPHb8WHBhlRe9xjTHOPlN15BCeSQf610cmLU1w\nSESSION_SECURE_ONLY=true\nSESSION_MAX_AGE=259200000\nSESSION_DOMAIN=changeme.cleverapps.io\nSESSION_NAME=otoroshi-session\nUSER_AGENT=otoroshi\n```\n"},{"name":"index.md","id":"/deploy/index.md","url":"/deploy/index.html","title":"Deploy to production","content":"# Deploy to production\n\nNow it's time to deploy Otoroshi in production, in this chapter we will see what kind of things you can do.\n\n@@@ index\n\n* [Clever Cloud](./clevercloud.md)\n* [AWS - Elastic Beanstalk](./aws-beanstalk.md)\n* [others](./other.md) \n* [Scaling](./scaling.md) \n\n@@@"},{"name":"other.md","id":"/deploy/other.md","url":"/deploy/other.html","title":"Others","content":"# Others\n\nOtoroshi can run wherever you want, even on a raspberry pi (Cluster^^) ;)\n\nThis section is not finished yet. So, as Otoroshi is available as a @ref:[Docker image](../getotoroshi/fromdocker.md) that you can run on any Docker compatible cloud, just go ahead and use it on cloud provider until we have more detailed documentation.\n\n## Running Otoroshi on AWS Elastic Beanstalk\n\nSee the @ref:[dedicated page to run Otoroshi on AWS Elastic Beanstalk](./aws-beanstalk.md)\n\n## Running Otoroshi on Amazon Elastic Container Service\n\nDeploy the @ref:[Docker image](../firstrun/run.md#from-docker) using [Amazon ECS](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html)\n\n## Running Otoroshi on GCE\n\nDeploy the @ref:[Docker image](../firstrun/run.md#from-docker) using [Google Compute Engine container integration](https://cloud.google.com/compute/docs/containers/deploying-containers)\n\n## Running Otoroshi on Azure\n\nDeploy the @ref:[Docker image](../firstrun/run.md#from-docker) using [Azure Container Service](https://azure.microsoft.com/en-us/services/container-service/)\n\n## Running Otoroshi on Heroku\n\nDeploy the @ref:[Docker image](../firstrun/run.md#from-docker) using [Docker integration](https://devcenter.heroku.com/articles/container-registry-and-runtime)\n\n## Running Otoroshi on CloudFoundry\n\nDeploy the @ref:[Docker image](../firstrun/run.md#from-docker) using [Docker integration](https://docs.cloudfoundry.org/adminguide/docker.html)\n\n## Running Otoroshi on your own infrastructure\n\nAs Otoroshi is a [Play Framework](https://www.playframework.com) application, you can read the doc about putting a `Play` app in production.\n\nhttps://www.playframework.com/documentation/2.6.x/ProductionConfiguration\n\nDownload the latest @ref:[Otoroshi distribution](../getotoroshi/frombinaries.md), unzip it, customize it and run it.\n"},{"name":"scaling.md","id":"/deploy/scaling.md","url":"/deploy/scaling.html","title":"Scaling Otoroshi","content":"# Scaling Otoroshi\n\n## Using multiple instances with a front load balancer\n\nOtoroshi has been designed to work with multiple instances. If you already have an infrastructure using frontal load balancing, you just have to declare Otoroshi instances as the target of all domain names handled by Otoroshi\n\n## Using master / workers mode of Otoroshi\n\nYou can read everything about it in @ref:[the clustering section](../topics/clustering.md) of the documentation.\n\n## Using IPVS\n\nYou can use [IPVS](https://en.wikipedia.org/wiki/IP_Virtual_Server) to load balance layer 4 traffic directly from the Linux Kernel to multiple instances of Otoroshi. You can find example of configuration [here](http://www.linuxvirtualserver.org/VS-DRouting.html) \n\n## Using DNS Round Robin\n\nYou can use [DNS round robin technique](https://en.wikipedia.org/wiki/Round-robin_DNS) to declare multiple A records under the domain names handled by Otoroshi.\n\n## Using software L4/L7 load balancers\n\nYou can use software L4 load balancers like NGINX or HAProxy to load balance layer 4 traffic directly from the Linux Kernel to multiple instances of Otoroshi.\n\nNGINX L7\n: @@snip [nginx-http.conf](../snippets/nginx-http.conf) \n\nNGINX L4\n: @@snip [nginx-tcp.conf](../snippets/nginx-tcp.conf) \n\nHA Proxy L7\n: @@snip [haproxy-http.conf](../snippets/haproxy-http.conf) \n\nHA Proxy L4\n: @@snip [haproxy-tcp.conf](../snippets/haproxy-tcp.conf) \n\n## Using a custom TCP load balancer\n\nYou can also use any other TCP load balancer, from a hardware box to a small js file like\n\ntcp-proxy.js\n: @@snip [tcp-proxy.js](../snippets/tcp-proxy.js) \n\ntcp-proxy.rs\n: @@snip [tcp-proxy.rs](../snippets/proxy.rs) \n\n"},{"name":"embedding.md","id":"/embedding.md","url":"/embedding.html","title":"Embedding Otoroshi","content":"# Embedding Otoroshi\n\nOtoroshi provides an API to start Otoroshi instances programmatically from any JVM app.\n\n## Getting the dependencies\n\nYou can get the Otoroshi dependency from bintray\n\nSbt\n: @@snip [build.sbt](./snippets/build.sbt)\n\nGradle\n: @@snip [build.gradle](./snippets/build.gradle)\n\n## Starting Otoroshi\n\nNow just instanciate an Otoroshi proxy with the configuration you like and you will be able to control it using the internal APIs of Otoroshi.\n\nScala\n: @@snip [embed.scala](./snippets/embed.scala)\n\nJava\n: @@snip [embed.java](./snippets/embed.java)\n"},{"name":"features.md","id":"/features.md","url":"/features.html","title":"Features ","content":"# Features \n\nAll the features supported by **Otoroshi** are listed below\n\n* Dynamic changes at runtime without full reload \n* Can proxy any HTTP/HTTP2 server\n* Can proxy websockets\n* Full featured admin Rest Api to control Otoroshi the way you want. Included, Swagger descriptor\n* Gorgeous React Web UI\n* Full end-to-end streaming of HTTP requests and responses\n* Completely non blocking and async internals\n* @ref:[Official Docker image](./getotoroshi/fromdocker.md)\n* @ref:[Multi backend datastore support](./firstrun/datastore.md)\n * Redis\n * In memory\n * Cassandra (experimental support)\n * Mongo (experimental support)\n * LevelDB (not suitable for production usage)\t\t\n* @ref:[Service is private (Api key access) by default with exclusions](./usage/2-services.md)\n* @ref:[Support wildcard domain names per service](./usage/2-services.md)\n* @ref:[Support routing headers for a service (ie. for service versioning)](./usage/2-services.md#service-meta)\n* @ref:[Support adding headers for a service request (ie. add Authorization header)](./usage/2-services.md#service-meta)\n* @ref:[Support custom html errors templates per service](./usage/2-services.md#custom-error-templates)\n* @ref:[Configurable circuit breaker and retries (with backoff) on network errors per service](./usage/2-services.md#service-circuit-breaker)\n* @ref:[Round Robin load balancing per service](./usage/2-services.md#targets)\n* @ref:[Services can be declared private (private apps) using an Auth0 domain](./usage/2-services.md#service-flags)\n* @ref:[Support for canary mode per service](./usage/2-services.md#canary-mode)\n* @ref:[Configurable health check per service](./usage/2-services.md#service-health-check)\n* @ref:[Support IP addresses blacklist per service (with wildcard support)](./usage/2-services.md#service-settings)\n* @ref:[Support IP addresses whitelist per service (with wildcard support)](./usage/2-services.md#service-settings)\n* @ref:[Support mutiple Api keys per service](./usage/3-apikeys.md)\n* @ref:[Support configurable throttling quota per Api key](./usage/3-apikeys.md#quotas)\n* @ref:[Support configurable daily quota per Api key](./usage/3-apikeys.md#quotas)\n* @ref:[Support configurable monthly quota per Api key](./usage/3-apikeys.md#quotas)\n* @ref:[Api keys are authorized for a group of services](./usage/3-apikeys.md)\n* @ref:[Api keys can be passed with custom headers, `Authorization: Basic ` headers, `Authorization: Bearer` or Cookies](./usage/3-apikeys.md)\n* @ref:[Add current Api key quotas usage in response headers](./usage/3-apikeys.md#quotas)\n* @ref:[Add current latencies in response headers](./usage/3-apikeys.md#quotas)\n* @ref:[Support service duplication through web UI](./usage/2-services.md#service-flags)\n* @ref:[Maintenance page per service](./usage/2-services.md#service-flags)\n* @ref:[Build page per service](./usage/2-services.md#service-flags)\n* @ref:[Force HTTPS usage per service](./usage/2-services.md#service-flags)\n* @ref:[Force secured exchanges with exclusions per service](./usage/2-services.md#service-flags)\n* @ref:[OpenAPI documentation displayed through web UI per service](./usage/2-services.md#service-settings)\n* @ref:[Live metrics per service (Rest)](./usage/4-monitor.md#service-live-stats)\n* @ref:[Metrics and analytics per service (if used with the Elastic connector)](./usage/4-monitor.md#service-analytics)\n* @ref:[Global metrics and analytics (if used with the Elastic connector)](./usage/7-metrics.md)\n* @ref:[Global audit log on admins actions](./usage/6-audit.md#audit-trail)\n* @ref:[Global alert log on admins actions](./usage/6-audit.md#alerts)\n* @ref:[Internal events can be sent outside using webhooks or Kafka topic](./setup/dangerzone.md#analytics-settings)\n* @ref:[Audit events can be sent outside using webhooks or Kafka topic](./setup/dangerzone.md#analytics-settings)\n* @ref:[Alerts events can be sent outside using webhooks or Kafka topic](./setup/dangerzone.md#analytics-settings)\n* @ref:[Alerts can be send to people by email using Mailgun](./integrations/mailgun.md)\n* @ref:[Support global IP addresses blacklist (with wildcard support)](./setup/dangerzone.md#whitelist-blacklist-settings)\n* @ref:[Support global IP addresses whitelist (with wildcard support)](./setup/dangerzone.md#whitelist-blacklist-settings)\n* @ref:[Support global endless responses for IP addresses (128 Gb of 0 for each response)](./setup/dangerzone.md#whitelist-blacklist-settings)\n* @ref:[Support global throttling quota](./setup/dangerzone.md#global-throttling-settings)\n* @ref:[Support global throttling quota per IP address (with wildcard support)](./setup/dangerzone.md#global-throttling-settings)\n* @ref:[Support global max number of concurrent connections](./setup/dangerzone.md#commons-settings)\n* Support global request tracing\n* @ref:[Support full JSON export of the reverse proxy state](./usage/8-importsexports.md#full-export)\n* @ref:[Support full JSON import of the reverse proxy state](./usage/8-importsexports.md#full-import)\n* @ref:[Support initial internal state import from JSON local file](./firstrun/configfile.md#db-configuration)\n* @ref:[Support initial internal state import from JSON file over HTTP](./firstrun/configfile.md#db-configuration)\n* @ref:[Enforce incoming JWT token verification and transformation](./topics/jwt-verifications.md)\n* @ref:[Introduce HTTP level chaos engineering pratices with the Snow Monkey](./topics/snow-monkey.md)\n* @ref:[Native support for service mesh architectures](./topics/service-mesh.md)\n* @ref:[Global live metrics](./setup/index.md#first-login)\n* @ref:[Send metrics to a StatsD/Datadog agent](./integrations/statsd.md)\n* @ref:[Advanced CleverCloud integration (create services from CleverCloud apps)](./integrations/clevercloud.md)\n* @ref:[Support admin login with any auth. module](./usage/9-auth.md)\n* @ref:[Support admin login with FIDO U2F device](./setup/admin.md#create-admin-user-with-u2f-device-login)\n* @ref:[Dynamic SSL termination](./topics/ssl.md)\n* @ref:[Rust CLI running on MacOS, Linux and Windows](./cli.md)\n* @ref:[Connectors to various HTTP services providers](./connectors/index.md)\n * [generic connector API](https://github.com/MAIF/otoroshi/tree/master/connectors/common)\n * @ref:[Clever Cloud](./connectors/clevercloud.md)\n * @ref:[Rancher](./connectors/rancher.md)\n * @ref:[Kubernetes](./connectors/kubernetes.md)\n"},{"name":"configfile.md","id":"/firstrun/configfile.md","url":"/firstrun/configfile.html","title":"Config. with files","content":"# Config. with files\n\nThere is a lot of things you can configure in Otoroshi. By default, Otoroshi provides a configuration that should be enough for testing purpose. But you'll likely need to update this configuration when you'll need to move into production.\n\nIn this page, any configuration property can be set at runtime using a `-D` flag when launching Otoroshi like\n\n```sh\njava -Dapp.domain=oto.tools -Dhttp.port=8080 -jar otoroshi.jar\n```\n\nor\n\n```sh\n./bin/otoroshi -Dhttp.port=8080 -Dapp.domain=oto.tools \n```\n\n## Common configuration\n\n| name | type | default value | description |\n| ---- |:----:| -------------- | ----- |\n| `app.domain` | string | \"oto.tools\" | the domain on which Otoroshi UI/API is be exposed|\n| `app.rootScheme` | string | \"http\" | the scheme on which Otoroshi is exposed, either \"http\" or \"https\" |\n| `app.snowflake.seed` | number | 0 | this number will is used to generate unique ids across the cluster. Each Otorshi instance must have a unique seed. |\n| `app.events.maxSize` | number | 1000 | max number of analytic and alert events stored locally |\n| `app.backoffice.exposed` | boolean | true | does the current Otoroshi instance exposed a backoffice ui|\n| `app.backoffice.subdomain` | string | \"otoroshi\" | the subdomain on wich Otoroshi backoffice will be served |\n| `app.backoffice.session.exp` | number | 86400000 | the number of seconds before the Otoroshi backoffice session expires |\n| `app.privateapps.subdomain` | string | \"privateapps\" | the subdomain on which private apps UI are served |\n| `app.privateapps.session.exp` | number | 86400000 | the number of seconds before the private apps session expires |\n| `app.claim.sharedKey` | string | \"secret\" | the shared secret used for signing the JWT token passed between Otoroshi and backend services |\n| `app.webhooks.size` | number | 100 | number of events sent at most when calling one of the analytics webhooks |\n| `app.throttlingWindow` | number | 10 | time window (in seconds) used to compute throttling quotas for ApiKeys |\n\n## Admin API configuration\n\nWhen Otoroshi starts for the first time, its datastore is empty. As Otoroshi uses Otoroshi to expose its admin REST API, you'll have to provide the details for the admin API exposition. **This part is super important** because if you go to production with the default values, your Otoroshi server won't be secured anymore.\n\n@@@ warning\nYOU HAVE TO CUSTOMIZE THE FOLLOWING VALUES BEFORE GOING TO PRODUCTION !!\n@@@\n\nSome of the following terms will seem obscure to you, but you will learn their meaning in the following chapters :)\n\n| name | type | default value | description |\n| ---- |:----:| -------------- | ----- |\n| `app.adminapi.exposed` | boolean | true | does the current Otoroshi instance expose an admin API |\n| `app.adminapi.targetSubdomain` | string | \"otoroshi-admin-internal-api\" | the subdomain on wich admin API call will be redirected from `app.adminapi.exposedSubdomain` |\n| `app.adminapi.exposedSubdomain` | string | \"otoroshi-api\" | the subdomain on wich the Otoroshi admin API will be exposed |\n| `app.adminapi.defaultValues.backOfficeGroupId` | string | \"admin-api-group\" | the name of the service groups that will contain the service descriptors for the Otoroshi admin API |\n| `app.adminapi.defaultValues.backOfficeApiKeyClientId` | string | \"admin-api-apikey-id\" | the client id of the Otoroshi admin API apikey |\n| `app.adminapi.defaultValues.backOfficeApiKeyClientSecret` | string | \"admin-api-apikey-secret\" | the client secret of the Otoroshi admin API apikey |\n| `app.adminapi.defaultValues.backOfficeServiceId` | string | \"admin-api-service\" | the id of the service descriptors for the Otoroshi admin API |\n| `app.adminapi.proxy.https` | boolean | false | whether or not the current Otoroshi instance serves its content over https. This setting is useful for the backoffice UI to access Otoroshi admin API |\n| `app.adminapi.proxy.local` | boolean | true | whether or not the admin API is accessible through `127.0.0.1`. This setting is useful for the backoffice UI to access Otoroshi admin API |\n\n## DB configuration\n\nAs Otoroshi supports multiple datastores, you'll have to provide some details about how to connect/configure it.\n\n| name | type | default value | description |\n| ---- |:----:| -------------- | ----- |\n| `app.storage` | string | \"inmemory\" | what kind of storage engine you want to use. Possible values are `inmemory`, `leveldb`, `redis`, `cassandra`, `mongo` |\n| `app.importFrom` | string | | a file path or a URL to an Otoroshi export file. If the datastore is empty on startup, this file will be used to import data to the empty DB |\n| `app.importFromHeaders` | array | [] | a list of `:` separated header to use if the `app.importFrom` setting is a URL |\n| `app.initialData` | object |  | object representing Otoroshi internal data as exported from danger zone so you don't need a config file and a data import file |\n| `app.redis.host` | string | \"localhost\" | the host of the redis server |\n| `app.redis.port` | number | 6379 | the port of the redis server |\n| `app.redis.slaves` | array | [] | the redis slaves lists |\n| `app.leveldb.path` | string | \"./leveldb\" | the path where levelDB files will be written |\n| `app.cassandra.hosts` | string | \"127.0.0.1\" | the host of the cassandra server |\n| `app.cassandra.host` | string | \"127.0.0.1\" | the list of cassandra hosts |\n| `app.cassandra.port` | number | 9042 | the port of the cassandra servers |\n| `app.mongo.uri` | string | \"mongodb://localhost:27017/default\" | the mongo URI following Mongo semantic https://docs.mongodb.com/manual/reference/connection-string/ |\n\n## Headers configuration\n\nOtoroshi uses a fair amount of http headers in order to work properly. The name of those headers are customizable to fit your needs.\n\n| name | type | default value | description |\n| ---- |:----:| -------------- | ----- |\n| `otoroshi.headers.trace.label` | string | \"Otoroshi-Viz-From-Label\" | header to pass request tracing informations |\n| `otoroshi.headers.trace.from` | string | \"Otoroshi-Viz-From\" | header to pass request tracing informations (ip address) |\n| `otoroshi.headers.trace.parent` | string | \"Otoroshi-Parent-Request\" | header to pass request tracing informations (parent request id) |\n| `otoroshi.headers.request.adminprofile` | string | \"Otoroshi-Admin-Profile\" | header to pass admin name when the admin API is called from the Otoroshi backoffice |\n| `otoroshi.headers.request.clientid` | string | \"Otoroshi-Client-Id\" | header to pass apikey client id |\n| `otoroshi.headers.request.clientsecret` | string | \"Otoroshi-Client-Secret\" | header to pass apikey client secret |\n| `otoroshi.headers.request.id` | string | \"Otoroshi-Request-Id\" | header containing the id of the current request |\n| `otoroshi.headers.response.proxyhost` | string | \"Otoroshi-Proxied-Host\" | header containing the proxied host |\n| `otoroshi.headers.response.error` | string | \"Otoroshi-Error\" | header containing whether or not the request generated an error |\n| `otoroshi.headers.response.errormsg` | string | \"Otoroshi-Error-Msg\" | header containing error message if some |\n| `otoroshi.headers.response.proxylatency` | string | \"Otoroshi-Proxy-Latency\" | header containing the current latency induced by Otoroshi |\n| `otoroshi.headers.response.upstreamlatency` | string | \"Otoroshi-Upstream-Latency\" | header containing the current latency from Otoroshi to service backend |\n| `otoroshi.headers.response.dailyquota` | string | \"Otoroshi-Daily-Calls-Remaining\" | header containing the number of remaining daily call (apikey) |\n| `otoroshi.headers.response.monthlyquota` | string | \"Otoroshi-Monthly-Calls-Remaining\" | header containing the number of remaining monthly call (apikey) |\n| `otoroshi.headers.comm.state` | string | \"Otoroshi-State\" | header containing a random value for secured mode |\n| `otoroshi.headers.comm.stateresp` | string | \"Otoroshi-State-Resp\" | header containing a random value for secured mode |\n| `otoroshi.headers.comm.claim` | string | \"Otoroshi-Claim\" | header containing a JWT token for secured mode |\n| `otoroshi.headers.healthcheck.test` | string | \"Otoroshi-Health-Check-Logic-Test\" | header containing a logic test for healthcheck |\n| `otoroshi.headers.healthcheck.testresult` | string | \"Otoroshi-Health-Check-Logic-Test-Result\" | header containing the result of a logic test for healthcheck |\n| `otoroshi.headers.jwt.issuer` | string | \"Otoroshi\" | the name of the issuer for the JWT token |\n| `otoroshi.headers.canary.tracker` | string | \"Otoroshi-Canary-Id\" | header containing the ID of the canary session if enabled |\n\n## Play specific configuration\n\nAs Otoroshi is a [Play app](https://www.playframework.com/), you should take a look at [Play configuration documentation](https://www.playframework.com/documentation/2.6.x/Configuration) to tune its internal configuration\n\n| name | type | default value | description |\n| ---- |:----:| -------------- | ----- |\n| `http.port` | number | 8080 | the http port used by Otoroshi. You can use 'disabled' as value if you don't want to use http |\n| `https.port` | number | disabled | the https port used by Otoroshi. You can use 'disabled' as value if you don't want to use https |\n| `http2.enabled` | boolean | false | whether or not http2 is enabled on the Otoroshi server. You need to configure https (listed bellow) to be able to use it |\n| `play.http.secret.key` | string | \"secret\" | the secret used to sign Otoroshi session cookie |\n| `play.http.session.secure` | boolean | false | whether or not the Otoroshi backoffice session will be served over https only |\n| `play.http.session.httpOnly` | boolean | true | whether or not the Otoroshi backoffice session will be accessible from Javascript |\n| `play.http.session.maxAge` | number | 259200000 | the number of seconds before Otoroshi backoffice session expired |\n| `play.http.session.domain` | string | \".oto.tools\" | the domain on which the Otoroshi backoffice session is authorized |\n| `play.http.session.cookieName` | string | \"otoroshi-session\" | the name of the Otoroshi backoffice session |\n| `play.ws.play.ws.useragent` | string | \"Otoroshi\" | the user agent sent by Otoroshi if not present on the original http request |\n| `play.server.https.keyStore.path` | string | | the path to the keystore containing the private key and certificate, if not provided generates a keystore for you |\n| `play.server.https.keyStore.type` | string | JKS | the key store type, defaults to JKS |\n| `play.server.https.keyStore.password` | string | '' | the password, defaults to a blank password |\n| `play.server.https.keyStore.algorithm` | string | | the key store algorithm, defaults to the platforms default algorithm |\n\n## More config. options\n\nSee https://github.com/MAIF/otoroshi/blob/master/otoroshi/conf/base.conf and https://github.com/MAIF/otoroshi/blob/master/otoroshi/conf/application.conf\n\nif you want to configure https on your Otoroshi server, just read [PlayFramework documentation about it](https://www.playframework.com/documentation/2.6.x/ConfiguringHttps)\n\n## Example of configuration file\n\n```conf\ninclude \"application.conf\"\n\nhttp.port = 8080\n\napp {\n storage = \"leveldb\"\n importFrom = \"./my-state.json\"\n env = \"prod\"\n domain = \"oto.tools\"\n rootScheme = \"http\"\n snowflake {\n seed = 0\n }\n events {\n maxSize = 1000\n }\n backoffice {\n subdomain = \"otoroshi\"\n session {\n exp = 86400000\n }\n }\n privateapps {\n subdomain = \"privateapps\"\n session {\n exp = 86400000\n }\n }\n adminapi {\n targetSubdomain = \"otoroshi-admin-internal-api\"\n exposedSubdomain = \"otoroshi-api\"\n defaultValues {\n backOfficeGroupId = \"admin-api-group\"\n backOfficeApiKeyClientId = \"admin-api-apikey-id\"\n backOfficeApiKeyClientSecret = \"admin-api-apikey-secret\"\n backOfficeServiceId = \"admin-api-service\"\n }\n }\n claim {\n sharedKey = \"mysecret\"\n }\n leveldb {\n path = \"./leveldb\"\n }\n}\n\nplay.http {\n session {\n secure = false\n httpOnly = true\n maxAge = 2592000000\n domain = \".oto.tools\"\n cookieName = \"oto-sess\"\n }\n}\n```\n"},{"name":"datastore.md","id":"/firstrun/datastore.md","url":"/firstrun/datastore.html","title":"Choose your datastore","content":"# Choose your datastore\n\nRight now, Otoroshi supports multiple datastore.\n\nYou can choose one datastore over another depending on your use case.\n\nAvailable datastores are the following :\n\n* in memory\n* redis\n* cassandra (experimental support)\n* mongodb (experimental support)\n* levelDB (not suitable for production usage)\n\nThe **levelDB** datastore is pretty handy for testing purposes, but is not supposed to be used in production mode.\n\nThe **in-memory** datastore is kind of interesting... It can be used for testing purposes, but it is also a good candidate for production because of its fastness. But in that case, you need to provide a way to feed the deployed in-memory instances after the initial boot. In a future release (i.e. not yet :D), we will provide a master/workers slave based on Otoroshi in memory instances and kafka (see https://github.com/MAIF/otoroshi/issues/8 for more details about the feature).\n\nThe **redis** datastore is quite nice when you want to easily deploy several Otoroshi instances.\n\nIf you need a datastore more scalable than redis, then you can use the **cassandra** datastore or the **mongodb** datastore.\n\nWe plan to add more datastores support in the future :)\n\n@@@ div { .centered-img }\n\n@@@\n"},{"name":"env.md","id":"/firstrun/env.md","url":"/firstrun/env.html","title":"Config. with ENVs","content":"# Config. with ENVs\n\nNow that you know @ref:[how to configure Otoroshi with the config. file](./configfile.md) every property in the following block can be overriden by an environment variable (an env. variable is written like `${?ENV_VARIABLE}`).\n\n```\napp.storage = ${?APP_STORAGE}\napp.importFrom = ${?APP_IMPORT_FROM}\napp.domain = ${?APP_DOMAIN}\napp.rootScheme = ${?APP_ROOT_SCHEME}\napp.throttlingWindow = ${?THROTTLING_WINDOW}\napp.snowflake.seed = ${?INSTANCE_NUMBER}\napp.events.maxSize = ${?MAX_EVENTS_SIZE}\napp.backoffice.exposed = ${?APP_BACKOFFICE_EXPOSED}\napp.backoffice.subdomain = ${?APP_BACKOFFICE_SUBDOMAIN}\napp.backoffice.session.exp = ${?APP_BACKOFFICE_SESSION_EXP}\napp.privateapps.subdomain = ${?APP_PRIVATEAPPS_SUBDOMAIN}\napp.privateapps.session.exp = ${?APP_PRIVATEAPPS_SESSION_EXP}\napp.adminapi.exposed = ${?ADMIN_API_EXPOSED}\napp.adminapi.targetSubdomain = ${?ADMIN_API_TARGET_SUBDOMAIN}\napp.adminapi.exposedSubdomain = ${?ADMIN_API_EXPOSED_SUBDOMAIN}\napp.adminapi.defaultValues.backOfficeGroupId = ${?ADMIN_API_GROUP}\napp.adminapi.defaultValues.backOfficeApiKeyClientId = ${?ADMIN_API_CLIENT_ID}\napp.adminapi.defaultValues.backOfficeApiKeyClientSecret = ${?ADMIN_API_CLIENT_SECRET}\napp.adminapi.defaultValues.backOfficeServiceId = ${?ADMIN_API_SERVICE_ID}\napp.adminapi.proxy.https = ${?ADMIN_API_HTTPS}\napp.adminapi.proxy.local = ${?ADMIN_API_LOCAL}\napp.claim.sharedKey = ${?CLAIM_SHAREDKEY}\napp.webhooks.size = ${?WEBHOOK_SIZE}\napp.redis.host = ${?REDIS_HOST}\napp.redis.port = ${?REDIS_PORT}\napp.redis.password = ${?REDIS_PASSWORD}\napp.redis.windowSize = ${?REDIS_WINDOW_SIZE}\napp.redis.useScan = ${?REDIS_USE_SCAN}\napp.inmemory.windowSize = ${?INMEMORY_WINDOW_SIZE}\napp.leveldb.windowSize = ${?LEVELDB_WINDOW_SIZE}\napp.leveldb.path = ${?LEVELDB_PATH}\napp.cassandra.windowSize = ${?CASSANDRA_WINDOW_SIZE}\napp.cassandra.hosts = ${?CASSANDRA_HOSTS}\napp.cassandra.host = ${?CASSANDRA_HOST}\napp.cassandra.port = ${?CASSANDRA_PORT}\napp.elastic.url = ${?ELASTIC_URL}\napp.elastic.user = ${?ELASTIC_USER}\napp.elastic.password = ${?ELASTIC_PASSWORD}\napp.elastic.index = ${?ELASTIC_INDEX}\napp.elastic.type = ${?ELASTIC_TYPE}\napp.mongo.uri = ${?MONGO_URI}\nhttp.port = ${?PORT}\nhttps.port = ${?HTTPS_PORT}\nhttp2.enabled = ${?HTTP2_ENABLED}\nplay.http.secret.key = ${?PLAY_CRYPTO_SECRET}\nplay.http.session.secure = ${?SESSION_SECURE_ONLY}\nplay.http.session.maxAge = ${?SESSION_MAX_AGE}\nplay.http.session.domain = ${?SESSION_DOMAIN}\nplay.http.session.cookieName = ${?SESSION_NAME}\nplay.ws.play.ws.useragent=${?USER_AGENT}\n```\n"},{"name":"host.md","id":"/firstrun/host.md","url":"/firstrun/host.html","title":"Setup your hosts","content":"# Setup your hosts\n\nBy default, Otoroshi starts with domain `oto.tools` that targets `127.0.0.1`. Of course you can change the domain, you have to add the values in your `/etc/hosts` file according to the setting you put in Otoroshi configuration\n\n* `app.domain` => `oto.tools`\n* `app.backoffice.subdomain` => `otoroshi`\n* `app.privateapps.subdomain` => `privateapps`\n* `app.adminapi.exposedSubdomain` => `otoroshi-api`\n* `app.adminapi.targetSubdomain` => `otoroshi-admin-internal-api`\n\nfor instance if you want to change the default domain and use something like `otoroshi.mydomain.org`, then start otoroshi like \n\n```sh\njava -Dapp.domain=mydomain.org -jar otoroshi.jar\n```\n\n@@@ warning\nOtoroshi cannot be accessed using `http://127.0.0.1:8080` or `http://localhost:8080` because Otoroshi uses Otoroshi to serve it's own UI and API. When otoroshi starts with an empty database, it will create a service descriptor for that using `app.domain` and the settings listed on this page and in the * [Config. with files page](./configfile.md) that serve Otoroshi API and UI on `http://otoroshi-api.${app.domain}` and `http://otoroshi.${app.domain}`.\nOnce the descriptor is saved in database, if you want to change `app.domain`, you'll have to edit the descriptor in the database or restart Otoroshi with an empty database.\n@@@\n"},{"name":"index.md","id":"/firstrun/index.md","url":"/firstrun/index.html","title":"First run","content":"# First run\n\nNow that you have your own distro of Otoroshi, it's time to run it. \n\nBut before doing so, you'll have to make some choices about some essential stuff in order to have your own customized version of Otoroshi.\n\nLet's start with the datastore\n\n\n@@@ index\n\n* [choose a datastore](./datastore.md)\n* [use custom config file](./configfile.md)\n* [use ENV](./env.md)\n* [initial state](./initialstate.md)\n* [Hosts](./host.md)\n* [Run](./run.md)\n\n@@@"},{"name":"initialstate.md","id":"/firstrun/initialstate.md","url":"/firstrun/initialstate.html","title":"Import initial state","content":"# Import initial state\n\nNow you are almost ready to run Otoroshi for the first time, but maybe you want to import data from previous Otoroshi installation in your current datastore.\n\nTo do that, you need to add the `app.importFrom` setting to the Otoroshi configuration (of `$APP_IMPORT_FROM` env).\n\nIt can be a file path or a URL\n\n## Example of export\n\n```json\n{\n \"config\": {\n \"lines\": [\"prod\"], \n \"limitConcurrentRequests\": true,\n \"maxConcurrentRequests\": 500,\n \"useCircuitBreakers\": true,\n \"apiReadOnly\": false,\n \"registerFromCleverHook\": false,\n \"u2fLoginOnly\": true,\n \"ipFiltering\": {\n \"whitelist\": [],\n \"blacklist\": []\n },\n \"throttlingQuota\": 100000,\n \"perIpThrottlingQuota\": 500,\n \"analyticsEventsUrl\": null,\n \"analyticsWebhooks\": [],\n \"alertsWebhooks\": [],\n \"alertsEmails\": [],\n \"endlessIpAddresses\": []\n },\n \"admins\": [],\n \"simpleAdmins\": [\n {\n \"username\": \"admin@otoroshi.io\",\n \"password\": \"xxxxxxxxxxxxxxxxx\",\n \"label\": \"Otoroshi Admin\",\n \"createdAt\": 1493971715708\n }\n ],\n \"serviceGroups\": [\n {\n \"id\": \"default\",\n \"name\": \"default-group\",\n \"description\": \"The default group\"\n },\n {\n \"id\": \"admin-api-group\",\n \"name\": \"Otoroshi Admin Api group\",\n \"description\": \"No description\"\n }\n ],\n \"apiKeys\": [\n {\n \"clientId\": \"admin-api-apikey-id\",\n \"clientSecret\": \"admin-api-apikey-secret\",\n \"clientName\": \"Otoroshi Backoffice ApiKey\",\n \"authorizedGroup\": \"admin-api-group\",\n \"enabled\": true,\n \"throttlingQuota\": 10000000,\n \"dailyQuota\": 10000000,\n \"monthlyQuota\": 10000000,\n \"metadata\": {}\n }\n ],\n \"serviceDescriptors\": [\n {\n \"id\": \"admin-api-service\",\n \"groupId\": \"admin-api-group\",\n \"name\": \"otoroshi-admin-api\",\n \"env\": \"prod\",\n \"domain\": \"oto.tools\",\n \"subdomain\": \"otoroshi-api\",\n \"targets\": [\n {\n \"host\": \"localhost:8080\",\n \"scheme\": \"http\"\n }\n ],\n \"root\": \"/\",\n \"enabled\": true,\n \"privateApp\": false,\n \"forceHttps\": false,\n \"maintenanceMode\": false,\n \"buildMode\": false,\n \"enforceSecureCommunication\": true,\n \"publicPatterns\": [],\n \"privatePatterns\": [],\n \"additionalHeaders\": {\n \"Host\": \"otoroshi-admin-internal-api.oto.tools\"\n },\n \"matchingHeaders\": {},\n \"ipFiltering\": {\n \"whitelist\": [],\n \"blacklist\": []\n },\n \"api\": {\n \"exposeApi\": false\n },\n \"healthCheck\": {\n \"enabled\": false,\n \"url\": \"/\"\n },\n \"metadata\": {}\n }\n ],\n \"errorTemplates\": []\n}\n```\n"},{"name":"run.md","id":"/firstrun/run.md","url":"/firstrun/run.html","title":"Run Otoroshi","content":"# Run Otoroshi\n\nNow you are ready to run Otoroshi. You can run the following command with some tweaks depending on the way you want to configure Otoroshi. If you want to pass a custom configuration file, use the `-Dconfig.file=/path/to/file.conf` flag in the following commands.\n\n## From .zip file\n\n```sh\nunzip otoroshi-dist.zip\ncd otoroshi-vx.x.x\n./bin/otoroshi\n```\n\n## From .jar file\n\nFor Java 8 & Java 11\n\n```sh\njava -jar otoroshi.jar\n```\n\n## From docker\n\n```sh\ndocker run -p \"8080:8080\" maif/otoroshi:1.4.8-dev\n```\n\nYou can also pass useful args like :\n\n```\ndocker run -p \"8080:8080\" otoroshi -Dconfig.file=/usr/app/otoroshi/conf/otoroshi.conf -Dlogger.file=/usr/app/otoroshi/conf/otoroshi.xml\n```\n\nIf you want to provide your own config file, you can read @ref:[the documentation about config files](../firstrun/configfile.md).\n\nYou can also provide some ENV variable using the `--env` flag to customize your Otoroshi instance.\n\nThe list of possible env variables is available @ref:[here](../firstrun/env.md).\n\nYou can use a volume to provide configuration like :\n\n```sh\ndocker run -p \"8080:8080\" -v \"$(pwd):/usr/app/otoroshi/conf\" maif/otoroshi\n```\n\nYou can also use a volume if you choose to use `leveldb` datastore like :\n\n```sh\ndocker run -p \"8080:8080\" -v \"$(pwd)/leveldb:/usr/app/otoroshi/leveldb\" maif/otoroshi -Dapp.storage=leveldb\n```\n\nYou can also use a volume if you choose to use exports files :\n\n```sh\ndocker run -p \"8080:8080\" -v \"$(pwd):/usr/app/otoroshi/imports\" maif/otoroshi -Dapp.importFrom=/usr/app/otoroshi/imports/export.json\n```\n\n## Run examples\n\n```sh\n$ java \\\n -Xms2G \\\n -Xmx8G \\\n -Dhttp.port=8080 \\\n -Dapp.importFrom=/home/user/otoroshi.json \\\n -Dconfig.file=/home/user/otoroshi.conf \\\n -jar ./otoroshi.jar\n\n[warn] otoroshi-in-memory-datastores - Now using InMemory DataStores\n[warn] otoroshi-env - The main datastore seems to be empty, registering some basic services\n[warn] otoroshi-env - Importing from: /home/user/otoroshi.json\n[info] play.api.Play - Application started (Prod)\n[info] p.c.s.AkkaHttpServer - Listening for HTTP on /0:0:0:0:0:0:0:0:8080\n```\n\nIf you choose to start Otoroshi without importing existing data, Otoroshi will create a new admin user and print the login details in the log. When you will log into the admin dashboard, Otoroshi will ask you to create another account to avoid security issues.\n\n```sh\n$ java \\\n -Xms2G \\\n -Xmx8G \\\n -Dhttp.port=8080 \\\n -jar otoroshi.jar\n\n[warn] otoroshi-in-memory-datastores - Now using InMemory DataStores\n[warn] otoroshi-env - The main datastore seems to be empty, registering some basic services\n[warn] otoroshi-env - You can log into the Otoroshi admin console with the following credentials: admin@otoroshi.io / HHUsiF2UC3OPdmg0lGngEv3RrbIwWV5W\n[info] play.api.Play - Application started (Prod)\n[info] p.c.s.AkkaHttpServer - Listening for HTTP on /0:0:0:0:0:0:0:0:8080\n```\n"},{"name":"frombinaries.md","id":"/getotoroshi/frombinaries.md","url":"/getotoroshi/frombinaries.html","title":"From binaries","content":"# From binaries\n\nIf you want to download the last version of Otoroshi and its CLI, you can grab them from the release page of the Otoroshi github page :\n\nGo to https://github.com/MAIF/otoroshi/releases and get the last version of the `otoroshi-dist.zip` file or `otoroshi.jar` file\n"},{"name":"fromdocker.md","id":"/getotoroshi/fromdocker.md","url":"/getotoroshi/fromdocker.html","title":"From docker","content":"# From docker\n\nIf you're a Docker aficionado, Otoroshi is provided as a Docker image that your can pull directly from Official repos.\n\nfirst, fetch the last Docker image of Otoroshi :\n\n```sh\ndocker pull maif/otoroshi:1.4.17\n# or \ndocker pull maif/otoroshi:latest\n# or \ndocker pull maif/otoroshi:jdk8-1.4.17\n# or \ndocker pull maif/otoroshi:jdk11-1.4.17\n# or \ndocker pull maif/otoroshi:jdk12-1.4.17\n# or \ndocker pull maif/otoroshi:jdk13-1.4.17\n# or \ndocker pull maif/otoroshi:jdk14-1.4.17\n```"},{"name":"fromsources.md","id":"/getotoroshi/fromsources.md","url":"/getotoroshi/fromsources.html","title":"From sources","content":"# From sources\n\nto build Otoroshi from sources, you need the following tools :\n\n* git\n* JDK 8\n* SBT\n* node\n* yarn\n\nOnce you've installed all those tools, go to the [Otoroshi github page](https://github.com/MAIF/otoroshi) and clone the sources :\n\n```sh\ngit clone https://github.com/MAIF/otoroshi.git --depth=1\n```\n\nthen you need to run the `build.sh` script to build the documentation, the React UI and the server :\n\n```sh\nsh ./scripts/build.sh\n```\n\nand that's all, you can grab your Otoroshi package at `otoroshi/target/scala-2.12/otoroshi` or `otoroshi/target/universal/`.\n\nFor those who want to build only parts of Otoroshi, read the following.\n\n## Build the documentation only\n\nGo to the `documentation` folder and run :\n\n```sh\nsbt ';clean;paradox'\n```\n\nThe documentation is located at `documentation/target/paradox/site/main/`\n\n## Build the React UI\n\nGo to the `otoroshi/javascript` folder and run :\n\n```sh\nyarn install\nyarn build\n```\n\nYou will find the JS bundle at `otoroshi/public/javascripts/bundle/bundle.js`.\n\n## Build the Otoroshi server\n\nGo to the `otoroshi` folder and run :\n\n```sh\nsbt ';clean;compile;dist;assembly'\n```\n\nYou will find your Otoroshi package at `otoroshi/target/scala-2.12/otoroshi` or `otoroshi/target/universal/`.\n"},{"name":"index.md","id":"/getotoroshi/index.md","url":"/getotoroshi/index.html","title":"Get Otoroshi","content":"# Get Otoroshi\n\nThere are several ways to get Otoroshi to run it on your system.\n\nLet's start with a good old build from sources :)\n\n@@@ index\n\n* [from sources](./fromsources.md)\n* [from binaries](./frombinaries.md)\n* [from docker](./fromdocker.md)\n\n@@@"},{"name":"index.md","id":"/index.md","url":"/index.html","title":"Otoroshi","content":"# Otoroshi\n\n**Otoroshi** is a layer of lightweight api management on top of a modern http reverse proxy written in Scala and developped by the MAIF OSS team that can handle all the calls to and between your microservices without service locator and let you change configuration dynamicaly at runtime.\n\n\n> *The Otoroshi is a large hairy monster that tends to lurk on the top of the torii gate in front of Shinto shrines. It's a hostile creature, but also said to be the guardian of the shrine and is said to leap down from the top of the gate to devour those who approach the shrine for only self-serving purposes.*\n\n@@@ div { .centered-img }\n[![Build Status](https://travis-ci.org/MAIF/otoroshi.svg?branch=master)](https://travis-ci.org/MAIF/otoroshi) [![Join the chat at https://gitter.im/MAIF/otoroshi](https://badges.gitter.im/MAIF/otoroshi.svg)](https://gitter.im/MAIF/otoroshi?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [ ![Download](https://img.shields.io/github/release/MAIF/otoroshi.svg) ](hhttps://github.com/MAIF/otoroshi/releases/download/v1.4.17/otoroshi.jar)\n@@@\n\n@@@ div { .centered-img }\n\n@@@\n\n## Installation\n\nYou can download the latest build of Otoroshi as a [fat jar](https://github.com/MAIF/otoroshi/releases/download/v1.4.17/otoroshi.jar), as a [zip package](https://github.com/MAIF/otoroshi/releases/download/v1.4.17/otoroshi-dist.zip) or as a @ref:[docker image](./getotoroshi/fromdocker.md).\n\nYou can install and run Otoroshi with this little bash snippet\n\n```sh\ncurl -L -o otoroshi.jar 'https://github.com/MAIF/otoroshi/releases/download/v1.4.17/otoroshi.jar'\njava -Dapp.domain=oto.tools -jar otoroshi.jar\n```\n\nor using docker\n\n```sh\ndocker run -p \"8080:8080\" -e \"APP_DOMAIN=oto.tools\" maif/otoroshi:1.4.17\n```\n\nnow open your browser to http://otoroshi.oto.tools:8080/, **log in with the credential generated in the logs** and explore by yourself, if you want better instructions, just go to the @ref:[Quick Start](./quickstart.md) or directly to the @ref:[installation instructions](./getotoroshi/index.md)\n\n## Documentation\n\n* @ref:[About Otoroshi](./about.md)\n* @ref:[Architecture](./archi.md)\n* @ref:[Features](./features.md)\n* @ref:[Try Otoroshi in 5 minutes](./quickstart.md)\n* @ref:[Video tutorials](./videos.md)\n* @ref:[Get Otoroshi](./getotoroshi/index.md)\n* @ref:[First run](./firstrun/index.md)\n* @ref:[Setup Otoroshi](./setup/index.md)\n* @ref:[Using Otoroshi](./usage/index.md)\n* @ref:[Third party Integrations](./integrations/index.md)\n* @ref:[Detailed topics](./topics/index.md)\n* @ref:[Embedding Otoroshi](./embedding.md)\n* @ref:[Admin REST API](./api.md)\n* @ref:[Rust CLI](./cli.md)\n* @ref:[Deploy to production](./deploy/index.md)\n* @ref:[Connectors](./connectors/index.md)\n\n## Discussion\n\nJoin the [Otoroshi](https://gitter.im/MAIF/otoroshi) channel on the [MAIF Gitter](https://gitter.im/MAIF)\n\n## Sources\n\nThe sources of Otoroshi are available on [Github](https://github.com/MAIF/otoroshi).\n\n## Logo\n\nYou can find the official Otoroshi logo [on GitHub](https://github.com/MAIF/otoroshi/blob/master/resources/otoroshi-logo.png). The Otoroshi logo has been created by François Galioto ([@fgalioto](https://twitter.com/fgalioto))\n\n## Changelog\n\nEvery release, along with the migration instructions, is documented on the [Github Releases](https://github.com/MAIF/otoroshi/releases) page.\n\n## Patrons\n\nThe work on Otoroshi was funded by MAIF with the help of the community.\n\n## Licence\n\nOtoroshi is Open Source and available under the [Apache 2 License](https://opensource.org/licenses/Apache-2.0)\n\n@@@ index\n\n* [About Otoroshi](about.md)\n* [Architecture](archi.md)\n* [Features](features.md)\n* [Quickstart](quickstart.md)\n* [Videos](videos.md)\n* [Get otoroshi](getotoroshi/index.md)\n* [First run](firstrun/index.md)\n* [Setup](setup/index.md)\n* [Using Otoroshi](usage/index.md)\n* [Integrations](integrations/index.md)\n* [Detailed topics](topics/index.md)\n* [Admin REST API](api.md)\n* [Embedding Otoroshi](./embedding.md)\n* [Official Rust CLI](cli.md)\n* [Deploy to production](deploy/index.md)\n* [Connectors](connectors/index.md)\n\n@@@\n"},{"name":"analytics.md","id":"/integrations/analytics.md","url":"/integrations/analytics.html","title":"Analytics","content":"# Analytics\n\nEach action and request on Otoroshi creates events that can be sent outside of Otoroshi for further usage. Those events can be sent using a webhook and/or through a Kafka topic.\n\n## Push events to Elasticsearch\n\nYou can use elastic search to store otoroshi events. To do this you have to configure the access to elasticsearch from `settings (cog icon) / Danger Zone` and expand the `Analytics: Elastic cluster (write)` section.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Read events from Elasticsearch\n\nYou can use elastic search to store otoroshi events. To do this you have to configure the access to elasticsearch from `settings (cog icon) / Danger Zone` and expand the `Analytics: Elastic dashboard datasource (read)` section.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Push events to WebHooks\n\nGo to `settings (cog icon) / Danger Zone` and expand the `Analytics: Webhooks` section.\n\n@@@ div { .centered-img }\n\n@@@\n\nHere you can configure the URL of the webhook and its headers if needed.\n\n## Push events to Kafka\n\nEvents can also be sent through a Kafka topic. Go to `settings (cog icon) / Danger Zone` and expand the `Analytics: Kafka` section.\n\n@@@ div { .centered-img }\n\n@@@\n\nFill the form, default values for topic names are :\n\n* `otoroshi-alerts`\n* `otoroshi-analytics`\n* `otoroshi-audits`\n\n@@@ warning\nIf you use trustore/keystore to access your kafka instances, the paths should be absolute and refers to host paths.\n@@@\n"},{"name":"auth0.md","id":"/integrations/auth0.md","url":"/integrations/auth0.html","title":"Auth0","content":"# Auth0\n\nYou can use Auth0 to log into Otoroshi's backoffice and Otoroshi's private apps.\n\nGo to `settings (cog icon) / Danger Zone` and expand the `Backoffice Auth0 settings` and `Private apps Auth0 settings` sections.\n\n@@@ div { .centered-img }\n\n@@@\n\nNow, you can fill the following fields :\n\n* `Client Id`\n* `Client Service`\n* `Domain`\n\nFor the `Callback URL` fields, use something like\n\n```\nhttps://otoroshi.oto.tools/backoffice/auth0/callback\nhttps://privateapps.oto.tools/privateapps/auth0/callback\n```\n\nOf course, you need to replace `otoroshi.oto.tools` and `privateapps.oto.tools` with your own domain and sub-domains. Don't forget to customize the callback URLs in your Auth0 backoffice too.\n\nNow if you logout, you will see the Auth0 option on the login screen\n\n@@@ div { .centered-img }\n\n@@@\n"},{"name":"clevercloud.md","id":"/integrations/clevercloud.md","url":"/integrations/clevercloud.html","title":"Clever Cloud","content":"# Clever Cloud\n\nOtoroshi provides an integration with Clever Cloud to create easily services based on application deployed on your Clever Cloud account.\nGo to `settings (cog icon) / Danger Zone` and expand the `CleverCloud settings` section.\n\n@@@ div { .centered-img }\n\n@@@\n\nFill the form with your CleverCloud credentials (https://www.clever-cloud.com/doc/clever-cloud-apis/cc-api/) and your CleverCloud `organization id`.\n\nOnce it's done, you will see a new menu in the side bar.\n\n@@@ div { .centered-img }\n\n@@@\n\nIf you click on it, you'll see a page listing all your apps deployed on Clever Cloud with buttons to create new services with the app as the target.\n\n@@@ div { .centered-img }\n\n@@@\n\nYou will also see a new button in the `Target` section of services to attach Clever Cloud applications as target for a service.\n\n@@@ div { .centered-img }\n\n@@@\n"},{"name":"index.md","id":"/integrations/index.md","url":"/integrations/index.html","title":"Third party Integrations","content":"# Third party Integrations\n\nOtoroshi provides some settings to interact with some third party systems.\n\n@@@ index\n\n* [Analytics](./analytics.md)\n* [Mailgun](./mailgun.md)\n* [StatsD / Datadog](./statsd.md)\n* [clevercloud](./clevercloud.md)\n\n@@@\n"},{"name":"mailgun.md","id":"/integrations/mailgun.md","url":"/integrations/mailgun.html","title":"Mailgun","content":"# Mailgun\n\nIf you want to receive Otoroshi alert by emails, you have to configure Otoroshi with your Mailgun credentials. Go to `settings (cog icon) / Danger Zone` and expand the `Mailgun settings` section.\n\n@@@ div { .centered-img }\n\n@@@\n\nFill the form with provided information on the `domain informations` page on Mailgun located at https://app.mailgun.com/app/domains/my.domain.\n\nThen, expand the `Alert settings` section and add email addresses separated by comma in the `Alert emails` field. **Don't forget to save.**\n\n@@@ div { .centered-img }\n\n@@@\n"},{"name":"statsd.md","id":"/integrations/statsd.md","url":"/integrations/statsd.html","title":"StatsD / Datadog","content":"# StatsD / Datadog\n\nOtoroshi provides a StatsD integration to monitor some technical metrics across all your Otoroshi instances.\nGo to `settings (cog icon) / Danger Zone` and expand the `Statsd settings` section.\n\n@@@ div { .centered-img }\n\n@@@\n\nAdd the host and port of the Statsd agent on your system.\nIf you're using Datadog, don't forget to check the `Datadog` switch.\n"},{"name":"quickstart.md","id":"/quickstart.md","url":"/quickstart.html","title":"Try Otoroshi in 5 minutes","content":"# Try Otoroshi in 5 minutes\n\nwhat you will need :\n\n* JDK 8\n* curl\n* 5 minutes of free time\n\nIf you don't/can't have these tools on your machine, you can start a sandboxed environment using here with the following command\n\n```sh\ndocker run -p \"8080:8080\" -it maif/otoroshi bash\n```\n\nor you can also try Otoroshi online with Google Cloud Shell if you're a user of the Google Cloud Platform\n\n@@@ div { .centered-img }\n[![Open in Cloud Shell](http://gstatic.com/cloudssh/images/open-btn.svg)](https://console.cloud.google.com/cloudshell/open?git_repo=https%3A%2F%2Fgithub.com%2Fmathieuancelin%2Fotoroshi-tutorial&page=shell&tutorial=tutorial.md)\n@@@\n\n## The elevator pitch\n\nOtoroshi is an awesome reverse proxy built with Scala that handles all the calls to and between your microservices without service locator and lets you change configuration dynamically at runtime.\n\n## I like sh but I really want to see the UI\n\nAs Otoroshi uses Otoroshi to serve its own admin UI and admin API, you won't be able to access the admin UI on `http://localhost:8080`. Otoroshi needs a domain name to know that you want to access the admin UI. By default, the admin UI is exposed on `http://otoroshi.oto.tools:8080`. Of course you can @ref:[configure](./firstrun/configfile.md#common-configuration) the domain of the subdomain. To configure access to the admin, just go to the [UI section of the quickstart](#what-about-the-ui).\n\n## Now some sh :)\n\n```sh\ncurl -L -o otoroshi.jar https://github.com/MAIF/otoroshi/releases/download/v1.4.17/otoroshi.jar\ncurl -L -o otoroshicli https://github.com/MAIF/otoroshi/releases/download/v1.4.17/linux-otoroshicli\n\nchmod +x otoroshicli\n\n# Run the Otoroshi server on Java 8\njava -jar otoroshi.jar &\n\n# Run the Otoroshi server on Java 9 and 10\njava --add-modules java.xml.bind -jar otoroshi.jar &\n\n# Check if admin api works\n./otoroshicli services all\n./otoroshicli apikeys all\n./otoroshicli groups all\n\n# Create a service that will proxy call to https://freegeoip.net through http://ip.geo.com:8080\n./otoroshicli services create --group default --name geo-ip-api --env prod \\\n --domain geo.com --subdomain ip --root /json/ --target https://freegeoip.net \\\n --public-pattern '/.*' --no-force-https\n\n# Then test it\n./otoroshicli tryout call \"http://127.0.0.1:8080/\" -X GET -H 'Host: ip.geo.com'\n# works with curl -X GET -H 'Host: ip.geo.com' \"http://127.0.0.1:8080/\" | jqn\n\n# Run 3 new microservices in 3 new terminal processes\n./otoroshicli tryout serve 9901\n./otoroshicli tryout serve 9902\n./otoroshicli tryout serve 9903\n\n# Create a service that will loadbalance between these 3 microservices and serves them through\n# http://api.hello.com:8080\n./otoroshicli services create --group default --id hello-api --name hello-api \\\n --env prod --domain hello.com --subdomain api --root / \\\n --target \"http://127.0.0.1:9901\" \\\n --target \"http://127.0.0.1:9902\" \\\n --public-pattern '/.*' --no-force-https --client-retries 3\n\n# Then test it multiple time to observe loadbalancing\n# You can also define '127.0.0.1 api.hello.com' in your /etc/hosts file and test it in your browser\n./otoroshicli tryout call \"http://127.0.0.1:8080/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\nsudo echo \"127.0.0.1 api.hello.com\" >> /etc/hosts\nopen \"http://api.hello.com:8080/\"\n\n# Then add a new target\n./otoroshicli services add-target hello-api --target=\"http://127.0.0.1:9903\"\n\n# Then test it multiple time to observe loadbalancing\n./otoroshicli tryout call \"http://127.0.0.1:8080/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\n\n# Then kill one of the microservices and test it multiple time to observe loadbalancing\n./otoroshicli tryout call \"http://127.0.0.1:8080/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\n\n# Then kill a second microservices and test it multiple time to observe loadbalancing\n./otoroshicli tryout call \"http://127.0.0.1:8080/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\n\n# Then kill the last microservices and test it to observe connection error\n./otoroshicli tryout call \"http://127.0.0.1:8080/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\n\n# Then delete your service\n./otoroshicli services delete hello-api\n```\n\n## What about the UI\n\nGo to http://otoroshi.oto.tools:8080/ and **log in with the credential generated in the logs** during first startup ;-)\n\nIf you want to know more about Otoroshi, you should continue reading the documentation starting with @ref:[how to get Otoroshi](./getotoroshi/index.md)\n\n## I don't have JDK 8 on my machine but I have Docker :)\n\nIf you want to use Docker, just follow these instructions\n\n```sh\n# here be careful, if you want to change the OTOROSHI_PORT, change the same value in the `otoroshicli.toml` config file\nexport OTOROSHI_PORT=8080\nexport LOCAL_IP_ADDRESS=999.999.999.999 # use your real local ip address here\n\ncurl -L -o otoroshicli https://github.com/MAIF/otoroshi/releases/download/v1.4.17/linux-otoroshicli\n\nchmod +x otoroshicli \n\ndocker run -p \"$OTOROSHI_PORT:8080\" maif/otoroshi &\n\n# Check if admin api works\n./otoroshicli services all\n./otoroshicli apikeys all\n./otoroshicli groups all\n\n# Create a service that will proxy call to https://freegeoip.net through http://ip.geo.com:$OTOROSHI_PORT\n./otoroshicli services create --group default --name geo-ip-api --env prod \\\n --domain geo.com --subdomain ip --root /json/ --target https://freegeoip.net \\\n --public-pattern '/.*' --no-force-https\n\n# Then test it\n./otoroshicli tryout call \"http://127.0.0.1:$OTOROSHI_PORT/\" -X GET -H 'Host: ip.geo.com'\n# works with curl -X GET -H 'Host: ip.geo.com' \"http://127.0.0.1:$OTOROSHI_PORT/\" | jqn\n\n# Run 3 new microservices in 3 new terminal processes\n./otoroshicli tryout serve 9901\n./otoroshicli tryout serve 9902\n./otoroshicli tryout serve 9903\n\n# Create a service that will loadbalance between these 3 microservices and serves them through\n# http://api.hello.com:$OTOROSHI_PORT\n./otoroshicli services create --group default --id hello-api --name hello-api \\\n --env prod --domain hello.com --subdomain api --root / \\\n --target \"http://$LOCAL_IP_ADDRESS:9901\" \\\n --target \"http://$LOCAL_IP_ADDRESS:9902\" \\\n --public-pattern '/.*' --no-force-https --client-retries 3\n\n# Then test it multiple time to observe loadbalancing\n# You can also define '127.0.0.1 api.hello.com' in your /etc/hosts file and test it in your browser\n./otoroshicli tryout call \"http://127.0.0.1:$OTOROSHI_PORT/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\nsudo echo \"127.0.0.1 api.hello.com\" >> /etc/hosts\nopen \"http://api.hello.com:$OTOROSHI_PORT/\"\n\n# Then add a new target\n./otoroshicli services add-target hello-api --target=\"http://$LOCAL_IP_ADDRESS:9903\"\n\n# Then test it multiple time to observe loadbalancing\n./otoroshicli tryout call \"http://127.0.0.1:$OTOROSHI_PORT/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\n\n# Then kill one of the microservices and test it multiple time to observe loadbalancing\n./otoroshicli tryout call \"http://127.0.0.1:$OTOROSHI_PORT/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\n\n# Then kill a second microservices and test it multiple time to observe loadbalancing\n./otoroshicli tryout call \"http://127.0.0.1:$OTOROSHI_PORT/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\n\n# Then kill the last microservices and test it to observe connection error\n./otoroshicli tryout call \"http://127.0.0.1:$OTOROSHI_PORT/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\n\n# Then delete your service\n./otoroshicli services delete hello-api\n```\n"},{"name":"admin.md","id":"/setup/admin.md","url":"/setup/admin.html","title":"Manage admin users","content":"# Manage admin users\n\n## Create admin user after the first run\n\nClick on the `Create an admin user` warning popup, or go to `settings (cog icon) / Admins`.\n\n@@@ div { .centered-img }\n\n@@@\n\nYou will see the list of registered admin users.\n\n@@@ div { .centered-img }\n\n@@@\n\nNow, enter informations about the new admin you want to create.\n\n@@@ div { .centered-img }\n\n@@@\n\nClick on `Register Admin`.\n\n@@@ div { .centered-img }\n\n@@@\n\nNow, you can discard the generated admin, confirm, then logout, login with the admin user you have just created and the danger popup will go away\n\n@@@ div { .centered-img }\n\n@@@\n\n## Create admin user with U2F device login\n\nGo to `settings (cog icon) / Admins`.\n\n@@@ div { .centered-img }\n\n@@@\n\nEnter informations about the new admin you want to create.\n\n@@@ div { .centered-img }\n\n@@@\n\nClick on `Register FIDO U2F Admin`.\n\nOtoroshi will ask you to plug your FIDO U2F device and touch it to complete registration.\n\n@@@ div { .centered-img }\n\n@@@\n\n@@@ warning\nTo be able to use FIDO U2F devices, Otoroshi must be served over https\n@@@\n\n## Discard admin user\n\nGo to `settings (cog icon) / Admins`, at the bottom of the page, you will see a list of admin users that you can discard. Just click on the `Discard User` button on the right side of the row and confirm that you actually want to discard an admin user.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Admin sessions management\n\nGo to `settings (cog icon) / Admins sessions`, you will see a list of active admin user sessions\n\n@@@ div { .centered-img }\n\n@@@\n\nYou can either discard single sessions one by one using the `Discard Session` on each targeted row of the list or discard all active sessions using the `Discard all sessions` button at the top of the page.\n"},{"name":"dangerzone.md","id":"/setup/dangerzone.md","url":"/setup/dangerzone.html","title":"Configure the Danger zone","content":"# Configure the Danger zone\n\nNow that you have an actual admin account, go to `setting (cog icon) / Danger Zone` in order to configure your Otoroshi instance.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Commons settings\n\nThis part allows you to configure various things :\n\n* `No Auth0 login` => allow you to disabled Auth0 login to the Otoroshi admin dashboard\n* `API read only` => disable `writes` on the Otoroshi admin api\n* `Use HTTP streaming` => use http streaming for each response. It should always be true\n* `Use circuit breakers` => allow usage of circuit breakers for each service\n* `Digitus medius` => change the character of endless HTTP responses from `0` to `🖕`\n* `Limit concurrent requests` => allow you to specify a max number of concurrent requests on an Otoroshi instance to avoid overloading\n* `Max concurrent requests` => max allowed number of concurrent requests on an Otoroshi instance to avoid overloading\n* `Max HTTP/1.0 response size` => max size of an HTTP/1.0 responses, because they are memory mapped\n* `Max local events` => number of events stored localy (alerts and audits)\n* `lines` => at least one (`prod`). for other, it will allow you to declare urls like `service.line.domain.tld`. For prod it will be `service.domain.tld`\n\n@@@ div { .centered-img }\n\n@@@\n\n## Whitelist / blacklist settings\n\nOtoroshi is capable of filtering request by ip address, allowing or blocking requests.\n\nOtoroshi also provides a fun feature called `Endless HTTP responses`. If you put an ip address in that field, then, for any http request on Otoroshi, every response will be 128 GB of `0`.\n\n@@@ div { .centered-img }\n\n@@@\n\n@@@ note\nNote that you may provide ip address with wildcard like the following `42.42.*.42` or `42.42.42.*` or `42.42.*.*`\n@@@\n\n## Global throttling settings\n\nOtoroshi is capable of managing throttling at a global level. Here you can configure number of authorized requests per second on a single Otoroshi instance and the number of authorized request per second for a unique ip address.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Analytics settings\n\nOne on the major features of Otoroshi is being able of generating internal events. Those events are not stored in Otoroshi's datastore but can be sent using `WebHooks`. You can configure those `WebHooks` from the `Danger Zone`.\n\nOtoroshi is also capable of reading some analytics and displays it from another MAIF product called `Omoïkane`. As Omoikane is not publicly available yet, is capable of storing events in an [Elastic](https://www.elastic.co/) cluster. For more information about analytics and what it does, just go to the @ref:[detailed chapter](../integrations/analytics.md)\n\n## Kafka settings\n\nOne on the major features of Otoroshi is being able of generating internal events. These events are not stored in Otoroshi's datastore but can be sent using a [Kafka message broker](https://kafka.apache.org/). You can configure Kafka access from the `Danger Zone`.\n\nBy default, Otoroshi's alert events will be sent on `otoroshi-alerts` topic, Otoroshi's audit events will be sent on `otoroshi-audits` topic and Otoroshi's traffic events will be sent on `otoroshi-analytics` topic.\n\n@@@ warning\nKeystore and truststore paths are optional local path on the server hosting Otoroshi\n@@@\n\n@@@ div { .centered-img }\n\n@@@\n\nFor more information about Kafka integration and what it does, just go to the @ref:[detailed chapter](../integrations/analytics.md)\n\n## Alerts settings\n\nEach time a dangerous action or something unusual is performed on Otoroshi, it will create an alert and store it. You can be notified for each of these alerts using `WebHooks` or emails. To do so, just add the `WebHook` URL and optional headers in the `Danger Zone` or any email address you want (you can add more than one email address).\n\n@@@ div { .centered-img }\n\n@@@\n\n## StatsD settings\n\nOtoroshi is capable of sending internal metrics to a StatsD agent. Just put the host and port of you StatsD agent in the `Danger Zone` to collect these metrics. If you using [Datadog](https://www.datadoghq.com), don't forget to check the dedicated button :)\n\n@@@ div { .centered-img }\n\n@@@\n\nFor more information about StatsD integration and what it does, just go to the @ref:[detailed chapter](../integrations/statsd.md)\n\n## Auth0 settings\n\nIt is possible to configure Otoroshi to allow admin users to log in through Auth0. Otoroshi also provides a feature called `Private apps.` that allows you to force login to an Auth0 domain before accessing an app. You can create an Auth0 client (https://manage.auth0.com/#/clients) for each of those features (admin users login and private apps login) and customize it with any rule you want (don't forget allowed callbacks, like `http://otoroshi.oto.tools:8080/backoffice/auth0/callback` and `http://privateapps.oto.tools:8080/backoffice/auth0/callback`).\n\nOnce you're done, go to the settings of each client (https://manage.auth0.com/#/clients/xxxxxxxxxxxxxxxx/settings) to get the information needed for the `Danger Zone`.\n\n@@@ div { .centered-img }\n\n@@@\n\n@@@ div { .centered-img }\n\n@@@\n\nFor more information about Auth0 integration and what it does, just go to the @ref:[detailed chapter](../integrations/auth0.md)\n\n## Mailgun settings\n\nIf you want to send emails for every alert generated by Otoroshi, you need to configure your Mailgun credentials in the `Danger Zone`. These parameters are provided in you Mailgun domain dashboard (i.e. https://app.mailgun.com/app/domains/my.domain.oto.tools) in the information section.\n\n@@@ div { .centered-img }\n\n@@@\n\nFor more information about Mailgun integration and what it does, just go to the @ref:[detailed chapter](../integrations/mailgun.md)\n\n## CleverCloud settings\n\nAs we built our products to run on Clever-Cloud, Otoroshi has a close integration with Clever-Cloud. In this section of `Danger Zone` you can configure how to access Clever-Cloud API.\n\nTo generate the needed value, please refers to [Clever-Cloud documentation](https://www.clever-cloud.com/doc/clever-cloud-apis/cc-api/)\n\n@@@ div { .centered-img }\n\n@@@\n\nFor more information about Clever-Cloud integration and what it does, just go to the @ref:[detailed chapter](../integrations/clevercloud.md)\n\n## Import / exports and panic mode\n\nFor more details about imports and exports, please go to the @ref:[dedicated chapter](../usage/8-importsexports.md)\n\nAbout panic mode, it's an unusual feature that allows you to discard all current admin. sessions, allows only admin users with U2F devices to log back, and pass the API in read-only mode. Only a person who has access to Otoroshi's datastore will be able to turn it back on.\n\n@@@ div { .centered-img }\n\n@@@\n"},{"name":"index.md","id":"/setup/index.md","url":"/setup/index.html","title":"Setup Otoroshi","content":"# Setup Otoroshi\n\nNow that Otoroshi is running, you are ready to log into the Otoroshi admin dashboard and setup your instance. Just go to :\n\nhttp://otoroshi.oto.tools:8080\n\nand you will see the following page\n \n@@@ div { .centered-img }\n\n@@@\n\nnow click on the login button and you will see the login page\n\n@@@ div { .centered-img }\n\n@@@\n\n@@@ warning\nUse the credentials generated in Otoroshi **logs** during **first run**.\n@@@\n\n@@@ div { .centered-img #first-login-example }\n\n@@@\n\n(of course, you can change this url dependending on the configuration you provided to Otoroshi).\n\nOnce logged in, the first screen you'll see should look like :\n\n@@@ div { .centered-img #first-login }\n\n@@@\n\nAs you can see, Otoroshi is not really happy about you being logged with a generated admin account.\n\nBut we will fix that in the next chapter\n\n@@@ index\n\n* [create admins](./admin.md)\n* [configure danger zone](./dangerzone.md)\n\n@@@\n"},{"name":"toc.md","id":"/toc.md","url":"/toc.html","title":"Table of contents","content":"# Table of contents\n\n@@toc\n\n"},{"name":"clustering.md","id":"/topics/clustering.md","url":"/topics/clustering.html","title":"Otoroshi clustering","content":"# Otoroshi clustering\n\nOtoroshi can work as a cluster by default as you can spin many Otoroshi servers using the same datastore or datastore cluster. In that case any instance is capable of serving services, Otoroshi admin UI, Otoroshi admin API, etc.\n\nBut sometimes, this is not enough. So Otoroshi provides an additional clustering model named `Leader / Workers` where there is a leader cluster ([control plane](https://en.wikipedia.org/wiki/Control_plane)), composed of Otoroshi instances backed by a datastore like Redis, Cassandra or Mongo, that is in charge of all `writes` to the datastore through Otoroshi admin UI and API, and a worker cluster ([data plane](https://en.wikipedia.org/wiki/Forwarding_plane)) composed of horizontally scalable Otoroshi instances, backed by a super fast in memory datastore, with the sole purpose of routing traffic to your services based on data synced from the leader cluster. With this distributed Otoroshi version, you can reach your goals of high availability, scalability and security.\n\nOtoroshi clustering only uses http internally (right now) to make communications between leaders and workers instances so it is fully compatible with PaaS providers like [Clever-Cloud](https://www.clever-cloud.com/en/) that only provide one external port for http traffic.\n\n@@@ div { .centered-img }\n\n\n*Fig. 1: Simplified view*\n@@@\n\n@@@ div { .centered-img }\n\n\n*Fig. 2: Deployment view*\n@@@\n\n## Cluster configuration\n\n```hocon\notoroshi {\n cluster {\n mode = \"leader\" # can be \"off\", \"leader\", \"worker\"\n compression = 4 # compression of the data sent between leader cluster and worker cluster. From -1 (disabled) to 9\n leader {\n name = ${?CLUSTER_LEADER_NAME} # name of the instance, if none, it will be generated\n urls = [\"http://127.0.0.1:8080\"] # urls to contact the leader cluster\n host = \"otoroshi-api.oto.tools\" # host of the otoroshi api in the leader cluster\n clientId = \"apikey-id\" # otoroshi api client id\n clientSecret = \"secret\" # otoroshi api client secret\n cacheStateFor = 4000 # state is cached during (ms)\n }\n worker {\n name = ${?CLUSTER_WORKER_NAME} # name of the instance, if none, it will be generated\n retries = 3 # number of retries when calling leader cluster\n timeout = 2000 # timeout when calling leader cluster\n state {\n retries = ${otoroshi.cluster.worker.retries} # number of retries when calling leader cluster on state sync\n pollEvery = 10000 # interval of time (ms) between 2 state sync\n timeout = ${otoroshi.cluster.worker.timeout} # timeout when calling leader cluster on state sync\n }\n quotas {\n retries = ${otoroshi.cluster.worker.retries} # number of retries when calling leader cluster on quotas sync\n pushEvery = 2000 # interval of time (ms) between 2 quotas sync\n timeout = ${otoroshi.cluster.worker.timeout} # timeout when calling leader cluster on quotas sync\n }\n }\n }\n}\n```\n\nyou can also use many env. variables to configure Otoroshi cluster\n\n```hocon\notoroshi {\n cluster {\n mode = ${?CLUSTER_MODE}\n compression = ${?CLUSTER_COMPRESSION}\n leader {\n name = ${?CLUSTER_LEADER_NAME}\n host = ${?CLUSTER_LEADER_HOST}\n url = ${?CLUSTER_LEADER_URL}\n clientId = ${?CLUSTER_LEADER_CLIENT_ID}\n clientSecret = ${?CLUSTER_LEADER_CLIENT_SECRET}\n groupingBy = ${?CLUSTER_LEADER_GROUP_BY}\n cacheStateFor = ${?CLUSTER_LEADER_CACHE_STATE_FOR}\n stateDumpPath = ${?CLUSTER_LEADER_DUMP_PATH}\n }\n worker {\n name = ${?CLUSTER_WORKER_NAME}\n retries = ${?CLUSTER_WORKER_RETRIES}\n timeout = ${?CLUSTER_WORKER_TIMEOUT}\n state {\n retries = ${?CLUSTER_WORKER_STATE_RETRIES}\n pollEvery = ${?CLUSTER_WORKER_POLL_EVERY}\n timeout = ${?CLUSTER_WORKER_POLL_TIMEOUT}\n }\n quotas {\n retries = ${?CLUSTER_WORKER_QUOTAS_RETRIES}\n pushEvery = ${?CLUSTER_WORKER_PUSH_EVERY}\n timeout = ${?CLUSTER_WORKER_PUSH_TIMEOUT}\n }\n }\n }\n}\n```\n\n@@@ warning\nYou **should** use HTTPS exposition for the Otoroshi API that will be used for data sync as sensitive informations are exchanged between control plane and data plane.\n@@@\n\n@@@ warning\nYou **must** have the same cluster configuration on every Otoroshi instance (worker/leader) with only names and mode changed for each instance. Some things in leader/worker are computed using configuration of their counterpart worker/leader.\n@@@\n\n## Cluster UI\n\nOnce an Otoroshi instance is launcher as cluster Leader, a new row of live metrics tile will be available on the home page of Otoroshi admin UI.\n\n@@@ div { .centered-img }\n\n@@@\n\nyou can also access a more detailed view of the cluster at `Settings (cog icon) / Cluster View`\n\n@@@ div { .centered-img }\n\n@@@\n\n## Run examples\n\nfor leader \n\n```sh\njava -Dhttp.port=8091 -Dhttps.port=9091 -Dotoroshi.cluster.mode=leader -jar otoroshi.jar\n```\n\nfor worker\n\n```sh\njava -Dhttp.port=8092 -Dhttps.port=9092 -Dotoroshi.cluster.mode=worker \\\n -Dotoroshi.cluster.leader.urls.0=http://127.0.0.1:8091 -jar otoroshi.jar\n```\n"},{"name":"index.md","id":"/topics/index.md","url":"/topics/index.html","title":"Detailed topics","content":"# Detailed topics\n\nIn this sections, you will find informations about various topics supported by Otoroshi\n\n@@@ index\n\n* [Chaos engineering with the Snow Monkey](./snow-monkey.md)\n* [Service mesh](./service-mesh.md)\n* [JWT Tokens verification](./jwt-verifications.md)\n* [SSL/TLS termination with Otoroshi](./ssl.md)\n* [Mutual TLS with Otoroshi](./mtls.md)\n* [Otoroshi Clustering](./clustering.md)\n* [Requests transformation](./req-transformers.md)\n* [Otoroshi monitoring](./monitoring.md)\n\n@@@\n"},{"name":"jwt-verifications.md","id":"/topics/jwt-verifications.md","url":"/topics/jwt-verifications.html","title":"JWT Tokens verification","content":"# JWT Tokens verification\n\nSometimes, it can be pretty useful to verify Jwt tokens coming from other provider on some services. Otoroshi provides a tool to do that per service. In the Service descriptor page, you can find a `Jwt token Verification` section dedicated to this topic.\n\n## Service descriptor local verifications\n\n@@@ div { .centered-img }\n\n@@@\n\nin this section you can select the type of verification you can choose if the verifier is local to the `Service descriptor` or reference a global one.\n\nYou can also enabled/disable jwt verification and activate strict mode. In strict mode, requests will be rejected if the jwt token is not found.\n\n### Jwt token location\n\nYou can use the `Source` selector to specify where the Jwt token can be found. \n\n* in a query string param\n\n@@@ div { .centered-img }\n\n@@@\n\n* in a header\n\n@@@ div { .centered-img }\n\n@@@\n\n* in a cookie\n\n@@@ div { .centered-img }\n\n@@@\n\n### Jwt signing\n\nYou can use the `Algo.` selector to specify the signing algorithm to use to verifiy the token\n\n@@@ div { .centered-img }\n\n@@@\n\nyou can choose between\n\n* Hmac + SHA256\n* Hmac + SHA384\n* Hmac + SHA512\n* RSA + SHA256\n* RSA + SHA384\n* RSA + SHA512\n* Elliptic Curve + SHA256\n* Elliptic Curve + SHA384\n* Elliptic Curve + SHA512\n\n@@@ div { .centered-img }\n\n@@@\n\nYou can use syntax like `${env.MY_ENV_VAR}` or `${config.my.config.path}` to provide secret/keys values. \n\n\n### Just verify signature and fields value\n\nUsing the `Verif. strategy` selector, you can choose `Verify jwt token`. This will verify if the token is signed using the settings from `jwt signing` section and the value of the fields provided in `Verify token fields`. Then the token will be send to the target just like that.\n\n@@@ div { .centered-img }\n\n@@@\n\n### Re-sign the token\n\nUsing the `Verif. strategy` selector, you can choose `Verify and re-sign jwt token`. This will verify if the token is signed using the settings from `jwt signing` section and the value of the fields provided in `Verify token fields`. Then the token will be re-signed using the settings provided in `Re-sign algo` and will be send to the target.\n\n@@@ div { .centered-img }\n\n@@@\n\n### Transform the token\n\nUsing the `Verif. strategy` selector, you can choose `Verify, re-sign and transform jwt token`. This will verify if the token is signed using the settings from `jwt signing` section and the value of the fields provided in `Verify token fields`. Then the token will be re-signed using the settings provided in `Re-sign algo`. You can also change the location of the token using `Token location`, remove fields using `Remove token fields`, set fields value using `Set token fields` and even rename fields using `Rename token fields`.\n\n@@@ div { .centered-img }\n\n@@@\n\nYou can also use a mini expression language in `Set token fields`. You just have to add expressions in values like `${expression}`. Supported expressions are the following :\n\n* `${date}` => set the current date\n* `${date.format('dd/MM/yyyy')}` => set the current date formatted with the format you want\n* `${token.fieldName}` => get the value of the field named `fieldName`\n* `${token.fieldName.replace('a', 'b')}` => get the value of the field named `fieldName` and replace `a` with `b`\n* `${token.fieldName.replaceAll('[0-9]', '-')}` => get the value of the field named `fieldName` and replace digits with `-`\n\nyou can of course use multiple expressions in one field like `my-value-is-${date}-with${token.user}`\n\n## Global verifications\n\nYou can create global jwt verifiers and reference them in your services (from the `Type` selector). When you set the type of verification to `Reference to a global definition`, you can choose an existing global jwt verifier\n\n@@@ div { .centered-img }\n\n@@@\n\nTo create a global verifier, go to `Settings (cog icon) / Global Jwt Verifiers` and it will display the list of global verifiers.\n\n@@@ div { .centered-img }\n\n@@@\n\nyou can them create, edit or delete verifiers\n\n@@@ div { .centered-img }\n\n@@@\n\n"},{"name":"monitoring.md","id":"/topics/monitoring.md","url":"/topics/monitoring.html","title":"Monitoring Otoroshi","content":"# Monitoring Otoroshi\n\nThe Otoroshi API exposes two endpoints for \n\n* `/health`: the health of the Otoroshi instance\n* `/metrics`: the metrics of the Otoroshi instance, either in JSON or Prometheus format using the `Accept` header (with `application/json` / `application/prometheus` values) or the `format` query param (with `json` or `prometheus` values)\n\n## Endpoints security\n\nThe two endpoints are exposed publicly on the Otoroshi admin api. But you can remove the corresponding public pattern and query the endpoints using standard apikeys. If you don't want to use apikeys but don't want to expose the endpoints publicly, you can defined two config. variables (`app.health.accessKey` or `HEALTH_ACCESS_KEY` and `otoroshi.metrics.accessKey` or `OTOROSHI_METRICS_ACCESS_KEY`) that will hold an access key for the endpoints. Then you can call the endpoints with an `access_key` query param with the value defined in the config. If you don't defined `otoroshi.metrics.accessKey` but define `app.health.accessKey`, `otoroshi.metrics.accessKey` will have the value of `app.health.accessKey`.\n \n## Examples\n\nlet say `app.health.accessKey` has value `MILpkVv6f2kG9Xmnc4mFIYRU4rTxHVGkxvB0hkQLZwEaZgE2hgbOXiRsN1DBnbtY`\n\n```sh\n$ curl http://otoroshi-api.oto.tools:8080/health\\?access_key\\=MILpkVv6f2kG9Xmnc4mFIYRU4rTxHVGkxvB0hkQLZwEaZgE2hgbOXiRsN1DBnbtY\n{\"otoroshi\":\"healthy\",\"datastore\":\"healthy\"}\n\n$ curl -H 'Accept: application/json' http://otoroshi-api.oto.tools:8080/metrics\\?access_key\\=MILpkVv6f2kG9Xmnc4mFIYRU4rTxHVGkxvB0hkQLZwEaZgE2hgbOXiRsN1DBnbtY\n{\"version\":\"4.0.0\",\"gauges\":{\"attr.app.commit\":{\"value\":\"xxxx\"},\"attr.app.id\":{\"value\":\"xxxx\"},\"attr.cluster.mode\":{\"value\":\"Leader\"},\"attr.cluster.name\":{\"value\":\"otoroshi-leader-0\"},\"attr.instance.env\":{\"value\":\"prod\"},\"attr.instance.id\":{\"value\":\"xxxx\"},\"attr.instance.number\":{\"value\":\"0\"},\"attr.jvm.cpu.usage\":{\"value\":136},\"attr.jvm.heap.size\":{\"value\":1409},\"attr.jvm.heap.used\":{\"value\":112},\"internals.0.concurrent-requests\":{\"value\":1},\"internals.global.throttling-quotas\":{\"value\":2},\"jvm.attr.name\":{\"value\":\"2085@xxxx\"},\"jvm.attr.uptime\":{\"value\":2296900},\"jvm.attr.vendor\":{\"value\":\"JDK11\"},\"jvm.gc.PS-MarkSweep.count\":{\"value\":3},\"jvm.gc.PS-MarkSweep.time\":{\"value\":261},\"jvm.gc.PS-Scavenge.count\":{\"value\":12},\"jvm.gc.PS-Scavenge.time\":{\"value\":161},\"jvm.memory.heap.committed\":{\"value\":1477967872},\"jvm.memory.heap.init\":{\"value\":1690304512},\"jvm.memory.heap.max\":{\"value\":3005218816},\"jvm.memory.heap.usage\":{\"value\":0.03916456777568639},\"jvm.memory.heap.used\":{\"value\":117698096},\"jvm.memory.non-heap.committed\":{\"value\":166445056},\"jvm.memory.non-heap.init\":{\"value\":7667712},\"jvm.memory.non-heap.max\":{\"value\":994050048},\"jvm.memory.non-heap.usage\":{\"value\":0.1523920694986979},\"jvm.memory.non-heap.used\":{\"value\":151485344},\"jvm.memory.pools.CodeHeap-'non-nmethods'.committed\":{\"value\":2555904},\"jvm.memory.pools.CodeHeap-'non-nmethods'.init\":{\"value\":2555904},\"jvm.memory.pools.CodeHeap-'non-nmethods'.max\":{\"value\":5832704},\"jvm.memory.pools.CodeHeap-'non-nmethods'.usage\":{\"value\":0.28408093398876405},\"jvm.memory.pools.CodeHeap-'non-nmethods'.used\":{\"value\":1656960},\"jvm.memory.pools.CodeHeap-'non-profiled-nmethods'.committed\":{\"value\":11796480},\"jvm.memory.pools.CodeHeap-'non-profiled-nmethods'.init\":{\"value\":2555904},\"jvm.memory.pools.CodeHeap-'non-profiled-nmethods'.max\":{\"value\":122912768},\"jvm.memory.pools.CodeHeap-'non-profiled-nmethods'.usage\":{\"value\":0.09536102872567315},\"jvm.memory.pools.CodeHeap-'non-profiled-nmethods'.used\":{\"value\":11721088},\"jvm.memory.pools.CodeHeap-'profiled-nmethods'.committed\":{\"value\":37355520},\"jvm.memory.pools.CodeHeap-'profiled-nmethods'.init\":{\"value\":2555904},\"jvm.memory.pools.CodeHeap-'profiled-nmethods'.max\":{\"value\":122912768},\"jvm.memory.pools.CodeHeap-'profiled-nmethods'.usage\":{\"value\":0.2538573047187417},\"jvm.memory.pools.CodeHeap-'profiled-nmethods'.used\":{\"value\":31202304},\"jvm.memory.pools.Compressed-Class-Space.committed\":{\"value\":14942208},\"jvm.memory.pools.Compressed-Class-Space.init\":{\"value\":0},\"jvm.memory.pools.Compressed-Class-Space.max\":{\"value\":367001600},\"jvm.memory.pools.Compressed-Class-Space.usage\":{\"value\":0.033858838762555805},\"jvm.memory.pools.Compressed-Class-Space.used\":{\"value\":12426248},\"jvm.memory.pools.Metaspace.committed\":{\"value\":99794944},\"jvm.memory.pools.Metaspace.init\":{\"value\":0},\"jvm.memory.pools.Metaspace.max\":{\"value\":375390208},\"jvm.memory.pools.Metaspace.usage\":{\"value\":0.25168142904782426},\"jvm.memory.pools.Metaspace.used\":{\"value\":94478744},\"jvm.memory.pools.PS-Eden-Space.committed\":{\"value\":349700096},\"jvm.memory.pools.PS-Eden-Space.init\":{\"value\":422576128},\"jvm.memory.pools.PS-Eden-Space.max\":{\"value\":1110966272},\"jvm.memory.pools.PS-Eden-Space.usage\":{\"value\":0.07505125052077188},\"jvm.memory.pools.PS-Eden-Space.used\":{\"value\":83379408},\"jvm.memory.pools.PS-Eden-Space.used-after-gc\":{\"value\":0},\"jvm.memory.pools.PS-Old-Gen.committed\":{\"value\":1127219200},\"jvm.memory.pools.PS-Old-Gen.init\":{\"value\":1127219200},\"jvm.memory.pools.PS-Old-Gen.max\":{\"value\":2253914112},\"jvm.memory.pools.PS-Old-Gen.usage\":{\"value\":0.014950035505168354},\"jvm.memory.pools.PS-Old-Gen.used\":{\"value\":33696096},\"jvm.memory.pools.PS-Old-Gen.used-after-gc\":{\"value\":23791152},\"jvm.memory.pools.PS-Survivor-Space.committed\":{\"value\":1048576},\"jvm.memory.pools.PS-Survivor-Space.init\":{\"value\":70254592},\"jvm.memory.pools.PS-Survivor-Space.max\":{\"value\":1048576},\"jvm.memory.pools.PS-Survivor-Space.usage\":{\"value\":0.59375},\"jvm.memory.pools.PS-Survivor-Space.used\":{\"value\":622592},\"jvm.memory.pools.PS-Survivor-Space.used-after-gc\":{\"value\":622592},\"jvm.memory.total.committed\":{\"value\":1644412928},\"jvm.memory.total.init\":{\"value\":1697972224},\"jvm.memory.total.max\":{\"value\":3999268864},\"jvm.memory.total.used\":{\"value\":269184904},\"jvm.thread.blocked.count\":{\"value\":0},\"jvm.thread.count\":{\"value\":82},\"jvm.thread.daemon.count\":{\"value\":11},\"jvm.thread.deadlock.count\":{\"value\":0},\"jvm.thread.deadlocks\":{\"value\":[]},\"jvm.thread.new.count\":{\"value\":0},\"jvm.thread.runnable.count\":{\"value\":25},\"jvm.thread.terminated.count\":{\"value\":0},\"jvm.thread.timed_waiting.count\":{\"value\":10},\"jvm.thread.waiting.count\":{\"value\":47}},\"counters\":{},\"histograms\":{},\"meters\":{},\"timers\":{}}\n\n$ curl -H 'Accept: application/prometheus' http://otoroshi-api.oto.tools:8080/metrics\\?access_key\\=MILpkVv6f2kG9Xmnc4mFIYRU4rTxHVGkxvB0hkQLZwEaZgE2hgbOXiRsN1DBnbtY\n# TYPE attr_jvm_cpu_usage gauge\nattr_jvm_cpu_usage 83.0\n# TYPE attr_jvm_heap_size gauge\nattr_jvm_heap_size 1409.0\n# TYPE attr_jvm_heap_used gauge\nattr_jvm_heap_used 220.0\n# TYPE internals_0_concurrent_requests gauge\ninternals_0_concurrent_requests 1.0\n# TYPE internals_global_throttling_quotas gauge\ninternals_global_throttling_quotas 3.0\n# TYPE jvm_attr_uptime gauge\njvm_attr_uptime 2372614.0\n# TYPE jvm_gc_PS_MarkSweep_count gauge\njvm_gc_PS_MarkSweep_count 3.0\n# TYPE jvm_gc_PS_MarkSweep_time gauge\njvm_gc_PS_MarkSweep_time 261.0\n# TYPE jvm_gc_PS_Scavenge_count gauge\njvm_gc_PS_Scavenge_count 12.0\n# TYPE jvm_gc_PS_Scavenge_time gauge\njvm_gc_PS_Scavenge_time 161.0\n# TYPE jvm_memory_heap_committed gauge\njvm_memory_heap_committed 1.477967872E9\n# TYPE jvm_memory_heap_init gauge\njvm_memory_heap_init 1.690304512E9\n# TYPE jvm_memory_heap_max gauge\njvm_memory_heap_max 3.005218816E9\n# TYPE jvm_memory_heap_usage gauge\njvm_memory_heap_usage 0.07680553268571043\n# TYPE jvm_memory_heap_used gauge\njvm_memory_heap_used 2.30817432E8\n# TYPE jvm_memory_non_heap_committed gauge\njvm_memory_non_heap_committed 1.66510592E8\n# TYPE jvm_memory_non_heap_init gauge\njvm_memory_non_heap_init 7667712.0\n# TYPE jvm_memory_non_heap_max gauge\njvm_memory_non_heap_max 9.94050048E8\n# TYPE jvm_memory_non_heap_usage gauge\njvm_memory_non_heap_usage 0.15262878997416435\n# TYPE jvm_memory_non_heap_used gauge\njvm_memory_non_heap_used 1.51720656E8\n# TYPE jvm_memory_pools_CodeHeap__non_nmethods__committed gauge\njvm_memory_pools_CodeHeap__non_nmethods__committed 2555904.0\n# TYPE jvm_memory_pools_CodeHeap__non_nmethods__init gauge\njvm_memory_pools_CodeHeap__non_nmethods__init 2555904.0\n# TYPE jvm_memory_pools_CodeHeap__non_nmethods__max gauge\njvm_memory_pools_CodeHeap__non_nmethods__max 5832704.0\n# TYPE jvm_memory_pools_CodeHeap__non_nmethods__usage gauge\njvm_memory_pools_CodeHeap__non_nmethods__usage 0.28408093398876405\n# TYPE jvm_memory_pools_CodeHeap__non_nmethods__used gauge\njvm_memory_pools_CodeHeap__non_nmethods__used 1656960.0\n# TYPE jvm_memory_pools_CodeHeap__non_profiled_nmethods__committed gauge\njvm_memory_pools_CodeHeap__non_profiled_nmethods__committed 1.1862016E7\n# TYPE jvm_memory_pools_CodeHeap__non_profiled_nmethods__init gauge\njvm_memory_pools_CodeHeap__non_profiled_nmethods__init 2555904.0\n# TYPE jvm_memory_pools_CodeHeap__non_profiled_nmethods__max gauge\njvm_memory_pools_CodeHeap__non_profiled_nmethods__max 1.22912768E8\n# TYPE jvm_memory_pools_CodeHeap__non_profiled_nmethods__usage gauge\njvm_memory_pools_CodeHeap__non_profiled_nmethods__usage 0.09610562183417755\n# TYPE jvm_memory_pools_CodeHeap__non_profiled_nmethods__used gauge\njvm_memory_pools_CodeHeap__non_profiled_nmethods__used 1.1812608E7\n# TYPE jvm_memory_pools_CodeHeap__profiled_nmethods__committed gauge\njvm_memory_pools_CodeHeap__profiled_nmethods__committed 3.735552E7\n# TYPE jvm_memory_pools_CodeHeap__profiled_nmethods__init gauge\njvm_memory_pools_CodeHeap__profiled_nmethods__init 2555904.0\n# TYPE jvm_memory_pools_CodeHeap__profiled_nmethods__max gauge\njvm_memory_pools_CodeHeap__profiled_nmethods__max 1.22912768E8\n# TYPE jvm_memory_pools_CodeHeap__profiled_nmethods__usage gauge\njvm_memory_pools_CodeHeap__profiled_nmethods__usage 0.25493618368435084\n# TYPE jvm_memory_pools_CodeHeap__profiled_nmethods__used gauge\njvm_memory_pools_CodeHeap__profiled_nmethods__used 3.1334912E7\n# TYPE jvm_memory_pools_Compressed_Class_Space_committed gauge\njvm_memory_pools_Compressed_Class_Space_committed 1.4942208E7\n# TYPE jvm_memory_pools_Compressed_Class_Space_init gauge\njvm_memory_pools_Compressed_Class_Space_init 0.0\n# TYPE jvm_memory_pools_Compressed_Class_Space_max gauge\njvm_memory_pools_Compressed_Class_Space_max 3.670016E8\n# TYPE jvm_memory_pools_Compressed_Class_Space_usage gauge\njvm_memory_pools_Compressed_Class_Space_usage 0.03386023385184152\n# TYPE jvm_memory_pools_Compressed_Class_Space_used gauge\njvm_memory_pools_Compressed_Class_Space_used 1.242676E7\n# TYPE jvm_memory_pools_Metaspace_committed gauge\njvm_memory_pools_Metaspace_committed 9.9794944E7\n# TYPE jvm_memory_pools_Metaspace_init gauge\njvm_memory_pools_Metaspace_init 0.0\n# TYPE jvm_memory_pools_Metaspace_max gauge\njvm_memory_pools_Metaspace_max 3.75390208E8\n# TYPE jvm_memory_pools_Metaspace_usage gauge\njvm_memory_pools_Metaspace_usage 0.25170985813247426\n# TYPE jvm_memory_pools_Metaspace_used gauge\njvm_memory_pools_Metaspace_used 9.4489416E7\n# TYPE jvm_memory_pools_PS_Eden_Space_committed gauge\njvm_memory_pools_PS_Eden_Space_committed 3.49700096E8\n# TYPE jvm_memory_pools_PS_Eden_Space_init gauge\njvm_memory_pools_PS_Eden_Space_init 4.22576128E8\n# TYPE jvm_memory_pools_PS_Eden_Space_max gauge\njvm_memory_pools_PS_Eden_Space_max 1.110966272E9\n# TYPE jvm_memory_pools_PS_Eden_Space_usage gauge\njvm_memory_pools_PS_Eden_Space_usage 0.17698545577448457\n# TYPE jvm_memory_pools_PS_Eden_Space_used gauge\njvm_memory_pools_PS_Eden_Space_used 1.96624872E8\n# TYPE jvm_memory_pools_PS_Eden_Space_used_after_gc gauge\njvm_memory_pools_PS_Eden_Space_used_after_gc 0.0\n# TYPE jvm_memory_pools_PS_Old_Gen_committed gauge\njvm_memory_pools_PS_Old_Gen_committed 1.1272192E9\n# TYPE jvm_memory_pools_PS_Old_Gen_init gauge\njvm_memory_pools_PS_Old_Gen_init 1.1272192E9\n# TYPE jvm_memory_pools_PS_Old_Gen_max gauge\njvm_memory_pools_PS_Old_Gen_max 2.253914112E9\n# TYPE jvm_memory_pools_PS_Old_Gen_usage gauge\njvm_memory_pools_PS_Old_Gen_usage 0.014950035505168354\n# TYPE jvm_memory_pools_PS_Old_Gen_used gauge\njvm_memory_pools_PS_Old_Gen_used 3.3696096E7\n# TYPE jvm_memory_pools_PS_Old_Gen_used_after_gc gauge\njvm_memory_pools_PS_Old_Gen_used_after_gc 2.3791152E7\n# TYPE jvm_memory_pools_PS_Survivor_Space_committed gauge\njvm_memory_pools_PS_Survivor_Space_committed 1048576.0\n# TYPE jvm_memory_pools_PS_Survivor_Space_init gauge\njvm_memory_pools_PS_Survivor_Space_init 7.0254592E7\n# TYPE jvm_memory_pools_PS_Survivor_Space_max gauge\njvm_memory_pools_PS_Survivor_Space_max 1048576.0\n# TYPE jvm_memory_pools_PS_Survivor_Space_usage gauge\njvm_memory_pools_PS_Survivor_Space_usage 0.59375\n# TYPE jvm_memory_pools_PS_Survivor_Space_used gauge\njvm_memory_pools_PS_Survivor_Space_used 622592.0\n# TYPE jvm_memory_pools_PS_Survivor_Space_used_after_gc gauge\njvm_memory_pools_PS_Survivor_Space_used_after_gc 622592.0\n# TYPE jvm_memory_total_committed gauge\njvm_memory_total_committed 1.644478464E9\n# TYPE jvm_memory_total_init gauge\njvm_memory_total_init 1.697972224E9\n# TYPE jvm_memory_total_max gauge\njvm_memory_total_max 3.999268864E9\n# TYPE jvm_memory_total_used gauge\njvm_memory_total_used 3.82665128E8\n# TYPE jvm_thread_blocked_count gauge\njvm_thread_blocked_count 0.0\n# TYPE jvm_thread_count gauge\njvm_thread_count 82.0\n# TYPE jvm_thread_daemon_count gauge\njvm_thread_daemon_count 11.0\n# TYPE jvm_thread_deadlock_count gauge\njvm_thread_deadlock_count 0.0\n# TYPE jvm_thread_new_count gauge\njvm_thread_new_count 0.0\n# TYPE jvm_thread_runnable_count gauge\njvm_thread_runnable_count 25.0\n# TYPE jvm_thread_terminated_count gauge\njvm_thread_terminated_count 0.0\n# TYPE jvm_thread_timed_waiting_count gauge\njvm_thread_timed_waiting_count 10.0\n# TYPE jvm_thread_waiting_count gauge\njvm_thread_waiting_count 47.0\n```"},{"name":"mtls.md","id":"/topics/mtls.md","url":"/topics/mtls.html","title":"Mutual TLS with Otoroshi","content":"# Mutual TLS with Otoroshi\n\nOtoroshi support mutual TLS out of the box. mTLS from client to Otoroshi and from Otoroshi to targets are supported. In this article we will see how to configure Otoroshi to use end-to-end mTLS. All code and files used in this articles can be found on the [Otoroshi github](https://github.com/MAIF/otoroshi/tree/master/demos/mtls)\n\n@@@ note { title=\"Experimental Feature\" }\nDynamic Mutual TLS is an experimental feature. It can change until it becomess an official feature\n@@@\n\n## End-to-end mTLS\n\nThe use case is the following :\n\n@@@ div { .centered-img }\n\n@@@\n\nfor this demo you will have to edit your `/etc/hosts` file to add the following entries\n\n```\n127.0.0.1 api.backend.lol api.frontend.lol www.backend.lol www.frontend.lol validation.backend.lol\n```\n\n### Create certificates\n\nBut first we need to generate some certificates to make the demo work\n\n```sh\nmkdir mtls-demo\ncd mtls-demo\nmkdir ca\nmkdir server\nmkdir client\n\n# create a certificate authority key, use password as pass phrase\nopenssl genrsa -out ./ca/ca-backend.key 4096\n# remove pass phrase\nopenssl rsa -in ./ca/ca-backend.key -out ./ca/ca-backend.key\n# generate the certificate authority cert\nopenssl req -new -x509 -sha256 -days 730 -key ./ca/ca-backend.key -out ./ca/ca-backend.cer -subj \"/CN=MTLSB\"\n\n\n# create a certificate authority key, use password as pass phrase\nopenssl genrsa -out ./ca/ca-frontend.key 2048\n# remove pass phrase\nopenssl rsa -in ./ca/ca-frontend.key -out ./ca/ca-frontend.key\n# generate the certificate authority cert\nopenssl req -new -x509 -sha256 -days 730 -key ./ca/ca-frontend.key -out ./ca/ca-frontend.cer -subj \"/CN=MTLSF\"\n\n\n# now create the backend cert key, use password as pass phrase\nopenssl genrsa -out ./server/_.backend.lol.key 2048\n# remove pass phrase\nopenssl rsa -in ./server/_.backend.lol.key -out ./server/_.backend.lol.key\n# generate the csr for the certificate\nopenssl req -new -key ./server/_.backend.lol.key -sha256 -out ./server/_.backend.lol.csr -subj \"/CN=*.backend.lol\"\n# generate the certificate\nopenssl x509 -req -days 365 -sha256 -in ./server/_.backend.lol.csr -CA ./ca/ca-backend.cer -CAkey ./ca/ca-backend.key -set_serial 1 -out ./server/_.backend.lol.cer\n# verify the certificate, should output './server/_.backend.lol.cer: OK'\nopenssl verify -CAfile ./ca/ca-backend.cer ./server/_.backend.lol.cer\n\n\n# now create the frontend cert key, use password as pass phrase\nopenssl genrsa -out ./server/_.frontend.lol.key 2048\n# remove pass phrase\nopenssl rsa -in ./server/_.frontend.lol.key -out ./server/_.frontend.lol.key\n# generate the csr for the certificate\nopenssl req -new -key ./server/_.frontend.lol.key -sha256 -out ./server/_.frontend.lol.csr -subj \"/CN=*.frontend.lol\"\n# generate the certificate\nopenssl x509 -req -days 365 -sha256 -in ./server/_.frontend.lol.csr -CA ./ca/ca-frontend.cer -CAkey ./ca/ca-frontend.key -set_serial 1 -out ./server/_.frontend.lol.cer\n# verify the certificate, should output './server/_.frontend.lol.cer: OK'\nopenssl verify -CAfile ./ca/ca-frontend.cer ./server/_.frontend.lol.cer\n\n\n# now create the client cert key for backend, use password as pass phrase\nopenssl genrsa -out ./client/_.backend.lol.key 2048\n# remove pass phrase\nopenssl rsa -in ./client/_.backend.lol.key -out ./client/_.backend.lol.key\n# generate the csr for the certificate\nopenssl req -new -key ./client/_.backend.lol.key -out ./client/_.backend.lol.csr -subj \"/CN=*.backend.lol\"\n# generate the certificate\nopenssl x509 -req -days 365 -sha256 -in ./client/_.backend.lol.csr -CA ./ca/ca-backend.cer -CAkey ./ca/ca-backend.key -set_serial 2 -out ./client/_.backend.lol.cer\n# generate a pkcs12 version of the cert and key, use password as password\nopenssl pkcs12 -export -clcerts -in client/_.backend.lol.cer -inkey client/_.backend.lol.key -out client/_.backend.lol.p12\n\n\n# now create the client cert key for frontend, use password as pass phrase\nopenssl genrsa -out ./client/_.frontend.lol.key 2048\n# remove pass phrase\nopenssl rsa -in ./client/_.frontend.lol.key -out ./client/_.frontend.lol.key\n# generate the csr for the certificate\nopenssl req -new -key ./client/_.frontend.lol.key -out ./client/_.frontend.lol.csr -subj \"/CN=*.frontend.lol\"\n# generate the certificate\nopenssl x509 -req -days 365 -sha256 -in ./client/_.frontend.lol.csr -CA ./ca/ca-frontend.cer -CAkey ./ca/ca-frontend.key -set_serial 2 -out ./client/_.frontend.lol.cer\n# generate a pkcs12 version of the cert and key, use password as password\nopenssl pkcs12 -export -clcerts -in client/_.frontend.lol.cer -inkey client/_.frontend.lol.key -out client/_.frontend.lol.p12\n```\n\nonce it's done, you should have something like\n\n```sh\n$ tree\n.\n├── backend.js\n├── ca\n│   ├── ca-backend.cer\n│   ├── ca-backend.key\n│   ├── ca-frontend.cer\n│   └── ca-frontend.key\n├── client\n│   ├── _.backend.lol.cer\n│   ├── _.backend.lol.csr\n│   ├── _.backend.lol.key\n│   ├── _.backend.lol.p12\n│   ├── _.frontend.lol.cer\n│   ├── _.frontend.lol.csr\n│   ├── _.frontend.lol.key\n│   └── _.frontend.lol.p12\n└── server\n ├── _.backend.lol.cer\n ├── _.backend.lol.csr\n ├── _.backend.lol.key\n ├── _.frontend.lol.cer\n ├── _.frontend.lol.csr\n └── _.frontend.lol.key\n\n3 directories, 18 files\n```\n\n### The backend service \n\nnow, let's create a backend service using nodejs. Create a file named `backend.js`\n\n```sh\ntouch backend.js\n```\n\nand put the following content\n\n```js\nconst fs = require('fs'); \nconst https = require('https'); \n\nconst options = { \n key: fs.readFileSync('./server/_.backend.lol.key'), \n cert: fs.readFileSync('./server/_.backend.lol.cer'), \n ca: fs.readFileSync('./ca/ca-backend.cer'), \n}; \n\nhttps.createServer(options, (req, res) => { \n res.writeHead(200, {\n 'Content-Type': 'application/json'\n }); \n res.end(JSON.stringify({ message: 'Hello World!' }) + \"\\n\"); \n}).listen(8444);\n```\n\nto run the server, just do \n\n```sh\nnode ./backend.js\n```\n\nnow you can try your server with\n\n```sh\ncurl --cacert ./ca/ca-backend.cer https://api.backend.lol:8444/\n# will print {\"message\":\"Hello World!\"}\n```\n\nnow modify your backend server to ensure that the client provides a client certificate like:\n\n```js\nconst fs = require('fs'); \nconst https = require('https'); \n\nconst options = { \n key: fs.readFileSync('./server/_.backend.lol.key'), \n cert: fs.readFileSync('./server/_.backend.lol.cer'), \n ca: fs.readFileSync('./ca/ca-backend.cer'), \n requestCert: true, \n rejectUnauthorized: true\n}; \n\nhttps.createServer(options, (req, res) => { \n console.log('Client certificate CN: ', req.socket.getPeerCertificate().subject.CN);\n res.writeHead(200, {\n 'Content-Type': 'application/json'\n }); \n res.end(JSON.stringify({ message: 'Hello World!' }) + \"\\n\"); \n}).listen(8444);\n```\n\nyou can test your new server with\n\n```sh\ncurl --cacert ./ca/ca-backend.cer --cert-type pkcs12 --cert ./client/_.backend.lol.p12:password https://api.backend.lol:8444/\n# will print {\"message\":\"Hello World!\"}\n```\n\n### Otoroshi setup\n\nDownload the latest version of the Otoroshi jar and run it like\n\n```sh\njava -jar otoroshi.jar\n\n[info] otoroshi-env - Admin API exposed on http://otoroshi-api.oto.tools:8080\n[info] otoroshi-env - Admin UI exposed on http://otoroshi.oto.tools:8080\n[info] otoroshi-in-memory-datastores - Now using InMemory DataStores\n[info] otoroshi-env - The main datastore seems to be empty, registering some basic services\n[info] otoroshi-env - You can log into the Otoroshi admin console with the following credentials: admin@otoroshi.io / xxxxxxxxxxxx\n[info] play.api.Play - Application started (Prod)\n[info] p.c.s.AkkaHttpServer - Listening for HTTP on /0:0:0:0:0:0:0:0:8080\n[info] p.c.s.AkkaHttpServer - Listening for HTTPS on /0:0:0:0:0:0:0:0:8443\n[info] otoroshi-env - Generating a self signed SSL certificate for https://*.oto.tools ...\n```\n\nand log into otoroshi with the tuple `admin@otoroshi.io / xxxxxxxxxxxx` displayed in the logs. Once logged in, create a new public service exposed on `http://api.frontend.lol` that targets `ahttps://api.backend.lol:8444/`.\n\n@@@ div { .centered-img }\n\n@@@\n\nand test it\n\n```sh\ncurl http://api.frontend.lol:8080/\n# the following error should be returned: {\"Otoroshi-Error\":\"Something went wrong, you should try later. Thanks for your understanding.\"}\n```\n\n@@@ warning\nAs seen before, the target of the otoroshi service is `ahttps://api.backend.lol:8444/`. `ahttps://` is not a typo and is intended. This tells otoroshi to use its experimental `http client` with dynamic tls support to fetch this resource.\n@@@\n\nyou should get an error due to the fact that Otoroshi doesn't know about the server certificate or the client certificate expected by the server.\n\nWe have to add the client certificate for `https://api.backend.lol` to Otoroshi. Go to http://otoroshi.oto.tools:8080/bo/dashboard/certificates and create a new item. Copy and paste the content of `./client/_.backend.lol.cer` and `./client/_.backend.lol.key` respectively in `Certificate full chain` and `Certificate private key`.\n\n@@@ div { .centered-img }\n\n@@@\n\nand retry the following curl command \n\n```sh\ncurl http://api.frontend.lol:8080/\n# the output should be: {\"message\":\"Hello World!\"}\n```\n\nnow we have to expose `https://api.frontend.lol:8443` using otoroshi. Go to http://otoroshi.oto.tools:8080/bo/dashboard/certificates and create a new item. Copy and paste the content of `./server/_.frontend.lol.cer` and `./server/_.frontend.lol.key` respectively in `Certificate full chain` and `Certificate private key`.\n\nand try the following command\n\n```sh\ncurl --cacert ./ca/ca-frontend.cer https://api.frontend.lol:8443/\n# the output should be: {\"message\":\"Hello World!\"}\n```\n\nnow we have to enforce the fact that we want client certificate for `api.frontend.lol`. To do that, we have to create a `Validation authority` in otoroshi and use it on the `api.frontend.lol` service. Go to http://otoroshi.oto.tools:8080/bo/dashboard/validation-authorities and create a new item. A validation authority is supposed to be a remote service that will say if the client certificate is valid. Here we don't really care if the certificate is valid or not, but we want to enforce the fact that there is a client certificate. So just check the `All cert. valid button`.\n\n@@@ div { .centered-img }\n\n@@@\n\nnow go back on your `api.frontend.lol` service, in the `Validation authority` section and select the authority you just created.\n\n@@@ div { .centered-img }\n\n@@@\n\nnow if you retry \n\n```sh\ncurl --cacert ./ca/ca-frontend.cer https://api.frontend.lol:8443/\n# the output should be: {\"Otoroshi-Error\":\"You're not authorized here !\"}\n```\n\nyou should get an error because no client cert. is passed with the request. But if you pass the `./client/_.frontend.lol.p12` client cert in your curl call\n\n```sh\ncurl --cacert ./ca/ca-frontend.cer --cert-type pkcs12 --cert ./client/_.frontend.lol.p12:password https://api.frontend.lol:8443/\n# the output should be: {\"message\":\"Hello World!\"}\n```\n\n### End to end test\n\nNow we can try to write a small nodejs client that uses our client certificates. Create a `client.js` file with the following code\n\n```js\nconst fs = require('fs'); \nconst https = require('https'); \n\nprocess.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0;\n\nconst options = { \n hostname: 'api.frontend.lol', \n port: 8443, \n path: '/', \n method: 'GET', \n key: fs.readFileSync('./client/_.frontend.lol.key'), \n cert: fs.readFileSync('./client/_.frontend.lol.cer'), \n ca: fs.readFileSync('./ca/ca-frontend.cer'), \n}; \n\nconst req = https.request(options, (res) => { \n console.log('statusCode', res.statusCode);\n console.log('headers', res.headers);\n console.log('body:');\n res.on('data', (data) => { \n process.stdout.write(data); \n }); \n}); \n\nreq.end(); \n\nreq.on('error', (e) => { \n console.error(e); \n});\n```\n\nand run the following command\n\n```sh\n$ node client.js\n# statusCode 200\n# headers { date: 'Mon, 10 Dec 2018 16:01:11 GMT',\n# connection: 'close',\n# 'transfer-encoding': 'chunked',\n# 'content-type': 'application/json' }\n# body:\n# {\"message\":\"Hello World!\"}\n```\n\nAnd that's it \n\n## Validating client certificates based on user identity\n\n@@@ note { title=\"Experimental Feature\" }\nValidation authorities is an experimental feature. It can change until it becomess an official feature\n@@@\n\nThe use case is the following :\n\n@@@ div { .centered-img }\n\n@@@\n\nthe idea here is to provide a unique client certificate per device that can access Otoroshi and use a validation authority to check if the user is allowed to access the underlying app with a specific device.\n\n### Generate client certificates for devices\n\nTo do that we are going to create two client certificates, one per device (let say for a laptop and a desktop computer). We are going to use the device serial number as common name of the certificate to be able to identify the device behind the certificate.\n\n```sh\nopenssl genrsa -out ./client/device-1.key 2048\nopenssl rsa -in ./client/device-1.key -out ./client/device-1.key\nopenssl req -new -key ./client/device-1.key -out ./client/device-1.csr -subj \"/CN=mbp-123456789\"\nopenssl x509 -req -days 365 -sha256 -in ./client/device-1.csr -CA ./ca/ca-frontend.cer -CAkey ./ca/ca-frontend.key -set_serial 3 -out ./client/device-1\nopenssl pkcs12 -export -clcerts -in client/device-1 -inkey client/device-1.key -out client/device-1.p12\n\nopenssl genrsa -out ./client/device-2.key 2048\nopenssl rsa -in ./client/device-2.key -out ./client/device-2.key\nopenssl req -new -key ./client/device-2.key -out ./client/device-2.csr -subj \"/CN=nuc-987654321\"\nopenssl x509 -req -days 365 -sha256 -in ./client/device-2.csr -CA ./ca/ca-frontend.cer -CAkey ./ca/ca-frontend.key -set_serial 4 -out ./client/device-2\nopenssl pkcs12 -export -clcerts -in client/device-2 -inkey client/device-2.key -out client/device-2.p12\n```\n\n### Setup actual validation\n\nnow we are going to write an validation authority (with mTLS too) that is going to respond on `https://validation.backend.lol:8445`. The server has access to a list of apps, users and devices to check if everything is correct. In this implementation, the lists are hardcoded, but you can write your own implementation that will fetch data from your corporate LDAP, CA, etc. Create a `validation.js` file and add the following content. Don't forget to do `yarn add x509` before running the server with `node validation.js`\n\n```js\nconst fs = require('fs'); \nconst https = require('https'); \nconst x509 = require('x509');\n\n// list of knwon apps\nconst apps = [\n {\n \"id\": \"iogOIDH09EktFhydTp8xspGvdaBq961DUDr6MBBNwHO2EiBMlOdafGnImhbRGy8z\",\n \"name\": \"my-web-service\",\n \"description\": \"A service that says hello\",\n \"host\": \"www.frontend.lol\"\n }\n];\n\n// list of known users\nconst users = [\n {\n \"name\": \"Mathieu\",\n \"email\": \"mathieu@oto.tools\",\n \"appRights\": [\n {\n \"id\": \"iogOIDH09EktFhydTp8xspGvdaBq961DUDr6MBBNwHO2EiBMlOdafGnImhbRGy8z\",\n \"profile\": \"user\",\n \"forbidden\": false\n },\n {\n \"id\": \"PqgOIDH09EktFhydTp8xspGvdaBq961DUDr6MBBNwHO2EiBMlOdafGnImhbRGy8z\",\n \"profile\": \"none\",\n \"forbidden\": true\n },\n ],\n \"ownedDevices\": [\n \"mbp-123456789\",\n \"nuc-987654321\",\n ]\n }\n];\n\n// list of known devices\nconst devices = [\n {\n \"serialNumber\": \"mbp-123456789\",\n \"hardware\": \"Macbook Pro 2018 13 inc. with TouchBar, 2.6 GHz, 16 Gb\",\n \"acquiredAt\": \"2018-10-01\",\n },\n {\n \"serialNumber\": \"nuc-987654321\",\n \"hardware\": \"Intel NUC i7 3.0 GHz, 32 Gb\",\n \"acquiredAt\": \"2018-09-01\",\n },\n {\n \"serialNumber\": \"iphone-1234\",\n \"hardware\": \"Iphone XS, 256 Gb\",\n \"acquiredAt\": \"2018-12-01\",\n }\n];\n\nconst options = { \n key: fs.readFileSync('./server/_.backend.lol.key'), \n cert: fs.readFileSync('./server/_.backend.lol.cer'), \n ca: fs.readFileSync('./ca/ca-backend.cer'), \n requestCert: true, \n rejectUnauthorized: true\n}; \n\nfunction readBody(request) {\n return new Promise((success, failure) => {\n const body = [];\n request.on('data', (chunk) => {\n body.push(chunk);\n }).on('end', () => {\n const bodyStr = Buffer.concat(body).toString();\n success(JSON.parse(bodyStr));\n });\n });\n}\n\nfunction chainIsValid(chain) {\n // validate cert dates\n // validate cert against clr\n // validate whatever you want here\n return true;\n}\n\nfunction call(req, res) {\n readBody(req).then(body => {\n const service = body.service;\n const email = (body.user || { email: 'mathieu@oto.tools' }).email; // here, should not be null if used with an otoroshi auth. module\n // common name should be device serial number\n const commonName = x509.getSubject(body.chain).commonName\n // search for a known device\n const device = devices.filter(d => d.serialNumber === commonName)[0];\n // search for a known user\n const user = users.filter(d => d.email === email)[0];\n // search for a known application\n const app = apps.filter(d => d.id === service.id)[0];\n res.writeHead(200, { 'Content-Type': 'application/json' }); \n if (chainIsValid(body.chain.map(x509.parseCert)) && user && device && app) {\n // check if the user actually owns the device\n const userOwnsDevice = user.ownedDevices.filter(d => d === device.serialNumber)[0];\n // check if the user has rights to access the app\n const rights = user.appRights.filter(d => d.id === app.id)[0];\n const hasRightToUseApp = !rights.forbidden\n if (userOwnsDevice && hasRightToUseApp) {\n // yeah !!!!\n console.log(`Call from user \"${user.email}\" with device \"${device.hardware}\" on app \"${app.name}\" with profile \"${rights.profile}\" authorized`)\n res.end(JSON.stringify({ status: 'good', profile: rights.profile }) + \"\\n\"); \n } else {\n // nope !!! nope, nope nope\n console.log(`Call from user \"${user.email}\" with device \"${device.hardware}\" on app \"${app.name}\" unauthorized because user doesn't owns the hardware or has no rights`)\n res.end(JSON.stringify({ status: 'unauthorized' }) + \"\\n\"); \n }\n } else {\n console.log(`Call unauthorized`)\n res.end(JSON.stringify({ status: 'unauthorized' }) + \"\\n\"); \n }\n });\n}\n\nhttps.createServer(options, call).listen(8445);\n```\n\nthe corresponding authority validation can be created in Otoroshi like \n\n```json\n{\n \"id\": \"r7m8j31rh66hhdia3ormfm0wfevu1kvg0zgaxsp3oxb6ivf7fy8kvygmvnrlxv81\",\n \"name\": \"Actual validation authority\",\n \"description\": \"Actual validation authority\",\n \"url\": \"ahttps://validation.backend.lol:8445\",\n \"host\": \"validation.backend.lol\",\n \"goodTtl\": 600000,\n \"badTtl\": 60000,\n \"method\": \"POST\",\n \"path\": \"/certificates/_validate\",\n \"timeout\": 10000,\n \"noCache\": false,\n \"alwaysValid\": false,\n \"headers\": {}\n}\n```\n\nbut you don't need to create it right now.\n\nTypically, a validation authority server is a server with a route on `POST /certificates/_validate` that accepts `application/json` and returns `application/json` with a body like\n\n```json\n{\n \"apikey\": nullable {\n \"clientId\": String,\n \"clientName\": String,\n \"authorizedGroup\": String,\n \"enabled\": Boolean,\n \"readOnly\": Boolean,\n \"allowClientIdOnly\": Boolean,\n \"throttlingQuota\": Long,\n \"dailyQuota\": Long,\n \"monthlyQuota\": Long,\n \"metadata\": Map[String, String]\n },\n \"user\": nullable {\n \"email\": String,\n \"name\": String,\n },\n \"service\": {\n \"id\": String,\n \"name\": String,\n \"groupId\": String,\n \"domain\": String,\n \"env\": String,\n \"subdomain\": String,\n \"root\": String,\n \"metadata\": String\n },\n \"chain\": PemFormattedCertificateChainString,\n \"fingerprints\": Array[String]\n}\n```\n\n\n### Setup Otoroshi\n\nYou can start Otoroshi and import data from the `state.json` file in the demo folder. The login tuple is `admin@otoroshi.io / password`. The `state.json` file contains everything you need for the demo, like certificates, service descriptors, auth. modules, etc ...\n\n```sh\njava -Dapp.importFrom=$(pwd)/state.json -Dapp.privateapps.port=8080 -jar otoroshi.jar\n\n[info] otoroshi-env - Admin API exposed on http://otoroshi-api.oto.tools:8080\n[info] otoroshi-env - Admin UI exposed on http://otoroshi.oto.tools:8080\n[info] otoroshi-in-memory-datastores - Now using InMemory DataStores\n[info] otoroshi-env - The main datastore seems to be empty, registering some basic services\n[info] otoroshi-env - Importing from: /pwd/state.json\n[info] play.api.Play - Application started (Prod)\n[info] otoroshi-env - Successful import !\n[info] p.c.s.AkkaHttpServer - Listening for HTTP on /0:0:0:0:0:0:0:0:8080\n[info] p.c.s.AkkaHttpServer - Listening for HTTPS on /0:0:0:0:0:0:0:0:8443\n```\n\n### Testing \n\nYou can test the service with curl like\n\n```sh\ncurl --cacert ./ca/ca-frontend.cer --cert-type pkcs12 --cert ./client/device-1.p12:password https://www.frontend.lol:8443/\n# output:

Hello World !!!

\ncurl --cacert ./ca/ca-frontend.cer --cert-type pkcs12 --cert ./client/device-2.p12:password https://www.frontend.lol:8443/\n# output:

Hello World !!!

\ncurl --cacert ./ca/ca-frontend.cer --cert-type pkcs12 --cert ./client/_.frontend.lol.p12:password https://www.frontend.lol:8443/\n# output: {\"Otoroshi-Error\":\"You're not authorized here !\"}\n```\n\nas expected, the first two call works as their common name is known by the validation server. The last one fails as it's not known.\n\n### Validate user identity\n\nNow let's try to setup firefox to provide the client certificate. Open firefox settings, go to `privacy settings and security` and click on `display certificates` at the bottom of the page. Here you can add the frontend CA (`./ca/ca-frontend.cer`) in the `Authorities` tab, check the 'authorize this CA to identify websites', and then in the `certificates` tab, import one of the devices `.p12` file (like `./client/device-1.p12`). Firefox will ask for the files password (it should be `password`).\n\n@@@ div { .centered-img }\n\n@@@\n\nNow restart firefox.\n\nNext, go to the `my-web-service` service in otoroshi (log in with `admin@otoroshi.io / password`) and activate `Enforce user login` in the Authentication section. It means that now, you'll have to log in when you'll go to https://www.frontend.lol:8443. With authentication activated on otoroshi, the user identity will be sent to the validation authority, so you can change the following line in the file `validation.js`\n\n```js\nconst email = (body.user || { email: 'mathieu@oto.tools' }).email; // here, should not be null if used with an otoroshi auth. module\n```\n\nto\n\n```js\nconst email = body.user.email;\n```\n\nThen, in Firefox, go to https://www.frontend.lol:8443/, firefox will ask which client certificate to use. Select the one you imported (in the process, maybe firefox will warn you that the certificate of the site is auto signed, just ignore it and continue ;) )\n\n@@@ div { .centered-img }\n\n@@@\n\nthen, you'll see a login screen from otoroshi. You can log in with `mathieu@oto.tools / password` and then you should see the hello world message.\n\n@@@ div { .centered-img }\n\n@@@\n\n### Going further with user authentication\n\nFor stronger user authentication, you can try to use an auth. module baked by a keycloak instance with yubikey as a strong second factor authentication instead of the basic auth. module we used previously in this article.\n"},{"name":"req-transformers.md","id":"/topics/req-transformers.md","url":"/topics/req-transformers.html","title":"Request transformers","content":"# Request transformers\n\nWhen everything has failed and you absolutely need a feature in Otoroshi to make your use case work, there is a solution. Request transformer is an experimental feature hidden behind a feature flag that allow you to code how Otoroshi should behave when receiving and rounting an http request. With request transformers, you can change request / response headers and request / response body to way you want.\n\n@@@ note { title=\"Experimental Feature\" }\nRequest transformers is an experimental feature. It can change until it becomess an official feature\n@@@\n\n@@@ warning { title=\"Use at your own risk\" }\nRequest transformers is a **very** powerful feature that allow you to do almost everything you want. But like any powerful feature, it comes with a price. The fact that you are responsible for how the request is transformed makes you responsible for all issues introduced by the transformer. If you block the current thread, introduce big latency, generate uncatched exceptions, etc. it's **your fault**. You have to ensure that your code is fast, non blocking, safe, etc. In any case, Otoroshi developers will not responsible for issues caused by a request transformer.\n\nAnd always remember this quote from ~~uncle Ben~~ Winston Churchill:\n\n

\"Where there is great power there is great responsibility\"

\n@@@\n\n## Enabling request transformers\n\nFirst you have to enable Otoroshi request transformers with the `-Dotoroshi.scripts.enabled=true` flag, or using env. variable `OTOROSHI_SCRIPTS_ENABLED=true`.\n\n## Anatomy of a transformer\n\nA request transformer is a piece of Scala code than can handle the complete lifecycle of an http request made on Otoroshi\n\n```scala\ncase class HttpRequest(url: String, method: String, headers: Map[String, String], query: Map[String, String], cookies: Seq[WSCookie] = Seq.empty[WSCookie]) {\n lazy val host: String = headers.getOrElse(\"Host\", \"\")\n lazy val uri: Uri = Uri(url)\n lazy val scheme: String = uri.scheme\n lazy val authority: Uri.Authority = uri.authority\n lazy val fragment: Option[String] = uri.fragment\n lazy val path: String = uri.path.toString()\n lazy val queryString: Option[String] = uri.rawQueryString\n lazy val relativeUri: String = uri.toRelative.toString()\n}\ncase class HttpResponse(status: Int, headers: Map[String, String], cookies: Seq[WSCookie] = Seq.empty[WSCookie])\n\ntrait RequestTransformer {\n\n /**\n * See RequestTransformer.transformRequest\n */\n def transformRequestSync(\n snowflake: String, // a cluster unique request id\n rawRequest: HttpRequest, // the http request as received by Otoroshi\n otoroshiRequest: HttpRequest, // the http request modified by Otoroshi, ready to be sent to the target service\n desc: ServiceDescriptor, // the service description for the request \n apiKey: Option[ApiKey] = None, // the api key used, if one\n user: Option[PrivateAppsUser] = None // the user, if one\n )(implicit \n env: Env, // the otoroshi environnment\n ec: ExecutionContext, // the threadpool for the request to perform async actions\n mat: Materializer // the akka materializer to work with streams\n ): Either[Result, HttpRequest] = {\n Right(otoroshiRequest)\n }\n\n /**\n * Transform the request forwarded from Otoroshi to the target service\n */\n def transformRequest(\n snowflake: String, // a cluster unique request id\n rawRequest: HttpRequest, // the http request as received by Otoroshi\n otoroshiRequest: HttpRequest, // the http request modified by Otoroshi, ready to be sent to the target service\n desc: ServiceDescriptor, // the service description for the request \n apiKey: Option[ApiKey] = None, // the api key used, if one\n user: Option[PrivateAppsUser] = None // the user, if one\n )(implicit \n env: Env, // the otoroshi environnment\n ec: ExecutionContext, // the threadpool for the request to perform async actions\n mat: Materializer // the akka materializer to work with streams\n ): Future[Either[Result, HttpRequest]] = { // return a future of Either. On the left side, an http error if there was en err. On the Right side, the transformed http request\n FastFuture.successful(transformRequestSync(snowflake, rawRequest, otoroshiRequest, desc, apiKey, user)(env, ec, mat))\n }\n\n /**\n * See RequestTransformer.transformResponse\n */\n def transformResponseSync(\n snowflake: String, // a cluster unique request id\n rawResponse: HttpResponse, // the http response as received by Otoroshi from the target service\n otoroshiResponse: HttpResponse, // the http response modified by Otoroshi, ready to be sent back to the client\n desc: ServiceDescriptor, // the service description for the request \n apiKey: Option[ApiKey] = None, // the api key used, if one\n user: Option[PrivateAppsUser] = None // the user, if one\n )(implicit \n env: Env, // the otoroshi environnment\n ec: ExecutionContext, // the threadpool for the request to perform async actions\n mat: Materializer // the akka materializer to work with streams\n ): Either[Result, HttpResponse] = {\n Right(otoroshiResponse)\n }\n\n /**\n * Transform the response from the target to the client\n */ \n def transformResponse(\n snowflake: String, // a cluster unique request id\n rawResponse: HttpResponse, // the http response as received by Otoroshi from the target service\n otoroshiResponse: HttpResponse, // the http response modified by Otoroshi, ready to be sent back to the client\n desc: ServiceDescriptor, // the service description for the request \n apiKey: Option[ApiKey] = None, // the api key used, if one\n user: Option[PrivateAppsUser] = None // the user, if one\n )(implicit \n env: Env, // the otoroshi environnment\n ec: ExecutionContext, // the threadpool for the request to perform async actions\n mat: Materializer // the akka materializer to work with streams\n ): Future[Either[Result, HttpResponse]] = { // return a future of Either. On the left side, an http error if there was en err. On the Right side, the transformed http response\n FastFuture.successful(transformResponseSync(snowflake, rawResponse, otoroshiResponse, desc, apiKey, user)(env, ec, mat))\n }\n\n /**\n * Transform the request body forwarded from Otoroshi to the target service\n * It uses akka-stream (see https://doc.akka.io/docs/akka/2.5/index.html)\n */\n def transformRequestBody(\n snowflake: String, // a cluster unique request id\n body: Source[ByteString, _], // the body of the request, a stream of byte array\n rawRequest: HttpRequest, // the http request as received by Otoroshi\n otoroshiRequest: HttpRequest, // the http request modified by Otoroshi, ready to be sent to the target service\n desc: ServiceDescriptor, // the service description for the request \n apiKey: Option[ApiKey] = None, // the api key used, if one\n user: Option[PrivateAppsUser] = None // the user, if one\n )(implicit \n env: Env, // the otoroshi environnment\n ec: ExecutionContext, // the threadpool for the request to perform async actions\n mat: Materializer // the akka materializer to work with streams\n ): Source[ByteString, _] = { // return a stream of byte array\n body\n }\n\n /**\n * Transform the response body forwarded from target to the client. \n * It uses akka-stream (see https://doc.akka.io/docs/akka/2.5/index.html)\n */\n def transformResponseBody(\n snowflake: String, // a cluster unique request id\n body: Source[ByteString, _], // the body of the response, a stream of byte array\n rawResponse: HttpResponse, // the http response as received by Otoroshi from the target service\n otoroshiResponse: HttpResponse, // the http response modified by Otoroshi, ready to be sent back to the client\n desc: ServiceDescriptor, // the service description for the request \n apiKey: Option[ApiKey] = None, // the api key used, if one\n user: Option[PrivateAppsUser] = None // the user, if one\n )(implicit \n env: Env, // the otoroshi environnment\n ec: ExecutionContext, // the threadpool for the request to perform async actions\n mat: Materializer // the akka materializer to work with streams\n ): Source[ByteString, _] = {\n body\n }\n}\n```\n\nfor more information about APIs you can use\n\n* https://www.playframework.com/documentation/2.6.x/api/scala/index.html#package\n* https://www.playframework.com/documentation/2.6.x/api/scala/index.html#play.api.mvc.Results\n* https://github.com/MAIF/otoroshi\n* https://doc.akka.io/docs/akka/2.5/stream/index.html\n* https://doc.akka.io/api/akka/current/akka/stream/index.html\n* https://doc.akka.io/api/akka/current/akka/stream/scaladsl/Source.html\n\n## Writing a transformer from Otoroshi UI\n\nLog into Otoroshi and go to `Settings (cog icon) / Scripts`. Here you can create multiple request transformer scripts and associate it with service descriptor later.\n\n@@@ div { .centered-img }\n\n@@@\n\nwhen you write an instance of a transformer in the Otoroshi UI, do the following\n\n```scala\nimport akka.stream.Materializer\nimport env.Env\nimport models.{ApiKey, PrivateAppsUser, ServiceDescriptor}\nimport otoroshi.script._\nimport play.api.Logger\nimport play.api.mvc.{Result, Results}\nimport scala.util._\nimport scala.concurrent.{ExecutionContext, Future}\n\nclass MyTransformer extends RequestTransformer {\n\n val logger = Logger(\"my-transformer\")\n\n // implements the methods you want\n}\n\n// WARN: do not forget this line to provide a working instance of your transformer to Otoroshi\nnew MyTransformer()\n```\n\nYou can use the compile button to check if the script compiles, or code the transformer in your IDE (see next point).\n\nThen go to a service descriptor, scroll to the bottom of the page, and select your transformer in the list\n\n@@@ div { .centered-img }\n\n@@@\n\n## Providing a transformer from Java classpath\n\nYou can write your own transformer using your favorite IDE. Just create an SBT project with the following dependencies. It can be quite handy to manage the source code like any other piece of code, and it avoid the compilation time for the script at Otoroshi startup.\n\n```scala\nlazy val root = (project in file(\".\")).\n settings(\n inThisBuild(List(\n organization := \"com.example\",\n scalaVersion := \"2.12.7\",\n version := \"0.1.0-SNAPSHOT\"\n )),\n name := \"request-transformer-example\",\n resolvers += Resolver.bintrayRepo(\"maif\", \"maven\"),\n libraryDependencies += \"fr.maif.otoroshi\" %% \"otoroshi\" % \"1.x.x\"\n )\n```\n\nWhen your code is ready, create a jar file \n\n```\nsbt package\n```\n\nand add the jar file to the Otoroshi classpath\n\n```sh\njava -Dotoroshi.scripts.enabled=true -cp \"/path/to/transformer.jar:$/path/to/otoroshi.jar\" play.core.server.ProdServerStart\n```\n\nthen, in your service descriptor, you can chose your transformer in the list. If you want to do it from the API, you have to defined the transformerRef using `cp:` prefix like \n\n```json\n{\n \"transformerRef\": \"cp:my.class.package.MyTransformer\"\n}\n```\n\n## Getting custom configuration from the Otoroshi config. file\n\nLet say you need to provide custom configuration values for a script, then you can customize a configuration file of Otoroshi\n\n```hocon\ninclude \"application.conf\"\n\notoroshi {\n scripts {\n enabled = true\n }\n}\n\nmy-transformer {\n env = \"prod\"\n maxRequestBodySize = 2048\n maxResponseBodySize = 2048\n}\n```\n\nthen start Otoroshi like\n\n```sh\njava -Dconfig.file=/path/to/custom.conf -jar otoroshi.jar\n```\n\nthen, in your transformer, you can write something like \n\n```scala\npackage com.example.otoroshi\n\nimport akka.stream.Materializer\nimport akka.stream.scaladsl._\nimport akka.util.ByteString\nimport env.Env\nimport models.{ApiKey, PrivateAppsUser, ServiceDescriptor}\nimport otoroshi.script._\nimport play.api.Logger\nimport play.api.mvc.{Result, Results}\nimport scala.util._\nimport scala.concurrent.{ExecutionContext, Future}\n\nclass BodyLengthLimiter extends RequestTransformer {\n\n override def transformResponseBody(\n snowflake: String,\n body: Source[ByteString, _],\n rawResponse: HttpResponse,\n otoroshiResponse: HttpResponse,\n desc: ServiceDescriptor,\n apiKey: Option[ApiKey],\n user: Option[PrivateAppsUser])(implicit env: Env, ec: ExecutionContext, mat: Materializer): Source[ByteString, _] = {\n val max = env.configuration.getOptional[Long](\"my-transformer.maxResponseBodySize\").getOrElse(Long.MaxValue)\n body.limitWeighted(max)(_.size)\n }\n\n override def transformRequestBody(\n snowflake: String,\n body: Source[ByteString, _],\n rawRequest: HttpRequest,\n otoroshiRequest: HttpRequest,\n desc: ServiceDescriptor,\n apiKey: Option[ApiKey],\n user: Option[PrivateAppsUser])(implicit env: Env, ec: ExecutionContext, mat: Materializer): Source[ByteString, _] = {\n val max = env.configuration.getOptional[Long](\"my-transformer.maxRequestBodySize\").getOrElse(Long.MaxValue)\n body.limitWeighted(max)(_.size)\n }\n}\n```\n\n## Using a library that is not embedded in Otoroshi\n\nJust use the `classpath` option when running Otoroshi\n\n```sh\njava -Dotoroshi.scripts.enabled=true -cp \"/path/to/library.jar:$/path/to/otoroshi.jar\" play.core.server.ProdServerStart\n```\n\nBe carefull as your library can conflict with other libraries used by Otoroshi and affect its stability\n"},{"name":"service-mesh.md","id":"/topics/service-mesh.md","url":"/topics/service-mesh.html","title":"Service mesh with Otoroshi","content":"# Service mesh with Otoroshi\n\n[Service mesh](http://philcalcado.com/2017/08/03/pattern_service_mesh.html) is a pattern that gained a lot of traction lately with tools like [Kubernetes](https://kubernetes.io/) and [Istio](https://istio.io/) using patterns like [sidecar container](https://kubernetes.io/blog/2015/06/the-distributed-system-toolkit-patterns/#example-1-sidecar-containers). It has the advantages to move the complexity of calling other services outside the application. For the application, it's just a local call and the proxy handles apikeys, throttling, quotas, resilience, tracing, etc ...\n\n## Architecture\n\nLet's say that we have 4 service that want to talk to each other. But we don't want to handle the complexity of calling those services with their own apikey, etc ...\n\n@@@ div { .centered-img }\n\n@@@\n\nSo we are going to build an infrastructure where each service has a sidecar Otoroshi that will handle the calls to other services\n\n@@@ div { .centered-img }\n\n@@@\n\n## Configuration\n\nIn this infrastructure, each service has its own service descriptor in Otoroshi that points to another Otoroshi instance. The challenge here is to dynamically change the target of any service when the current call is supposed to access th actual app.\n\nTo do that, each Otoroshi instance (that is part of the same cluster and use the same database) will have a configuration slightly different configuration than the other ones. \n\n```hocon\nsidecar {\n serviceId = \"my-local-service-id\" # the id of the local sidecar app\n target = \"http://127.0.0.1:56876\" # the local service target, it should be on 127.0.0.1 but can be on another ip address\n from = \"127.0.0.1\" # the ip address authorized to actualy access the local app, it should be on 127.0.0.1 but can be on another ip address\n strict = true # use actual remote address or remote address/proxied remote address\n apikey {\n clientId = \"my-apikey-id\" # the apikey client id to access other services\n }\n}\n```\n\n## Configuration using env. variables\n\nYou can also only env. variables to configure sidecar Otoroshi instances \n\n```sh\nSIDECAR_SERVICE_ID=my-local-service-id\nSIDECAR_TARGET=http://127.0.0.1:56876\nSIDECAR_FROM=127.0.0.1\nSIDECAR_STRICT=true\nSIDECAR_APIKEY_CLIENT_ID=my-apikey-id\n```\n\n## Example\n\nYou can find a full example of a simple service mesh using Otoroshi [here](https://github.com/MAIF/otoroshi/tree/master/demos/service-mesh)."},{"name":"snow-monkey.md","id":"/topics/snow-monkey.md","url":"/topics/snow-monkey.html","title":"Chaos engineering with the Snow Monkey","content":"# Chaos engineering with the Snow Monkey\n\nNihonzaru (the Snow Monkey) is the chaos engineering tool provided by Otoroshi. You can access it at `Settings (cog icon) / Snow Monkey`.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Chaos engineering\n\nOtoroshi offers some tools to introduce [chaos engineering](https://principlesofchaos.org/) in your everyday life. With chaos engineering, you will improve the resilience of your architecture by creating faults in production on running systems. With [Nihonzaru (the snow monkey)](https://en.wikipedia.org/wiki/Japanese_macaque) Otoroshi helps you to create faults on http request/response handled by Otoroshi. \n\n@@@ div { .centered-img }\n\n@@@\n\n## Settings\n\n@@@ div { .centered-img }\n\n@@@\n\nThe snow monkey let you define a few settings to work properly :\n\n* **Include user facing apps.**: you want to create fault in production, but maybe you don't want your users to enjoy some nice snow monkey generated error pages. Using this switch let you include of not user facing apps (ui apps). Each service descriptor has a `User facing app switch` that will be used by the snow monkey.\n* **Dry run**: when dry run is enabled, outages will be registered and will generate events and alerts (in the otoroshi eventing system) but requests won't be actualy impacted. It's a good way to prepare applications to the snow monkey habits\n* **Outage strategy**: Either `AllServicesPerGroup` or `OneServicePerGroup`. It means that only one service per group or all services per groups will have `n` outages (see next bullet point) during the snow monkey working period\n* **Outages per day**: during snow monkey working period, each service per group or one service per group will have only `n` outages registered \n* **Working period**: the snow monkey only works during a working period. Here you can defined when it starts and when it stops\n* **Outage duration**: here you can defined the bounds for the random outage duration when an outage is created on a service\n* **Impacted groups**: here you can define a list of service groups impacted by the snow monkey. If none is specified, then all service groups will be impacted\n\n## Faults\n\nWith the snow monkey, you can generate four types of faults\n\n* **Large request fault**: Add trailing bytes at the end of the request body (if one)\n* **Large response fault**: Add trailing bytes at the end of the response body\n* **Latency injection fault**: Add random response latency between two bounds\n* **Bad response injection fault**: Create predefined responses with custom headers, body and status code\n\nEach fault let you define a ratio for impacted requests. If you specify a ratio of `0.2`, then 20% of the requests for the impacte service will be impacted by this fault\n\n@@@ div { .centered-img }\n\n@@@\n\nThen you juste have to start the snow monkey and enjoy the show ;)\n\n@@@ div { .centered-img }\n\n@@@\n\n## Current outages\n\nIn the last section of the snow monkey page, you can see current outages (per service), when they started, their duration, etc ...\n\n@@@ div { .centered-img }\n\n@@@"},{"name":"ssl.md","id":"/topics/ssl.md","url":"/topics/ssl.html","title":"SSL/TLS termination with Otoroshi","content":"# SSL/TLS termination with Otoroshi\n\nOtoroshi can be used as an SSL/TLS termination. It is enabled by default but you can customise HTTPS port with `https.port` config. and env. var `HTTPS_PORT`. You can create upload any certificate you want in the Otoroshi UI or using the API. Just go to `settings (cog icon) / SSL certificates`.\n\n@@@ note { title=\"Experimental Feature\" }\nDynamic SSL/TLS termination is an experimental feature. It can change until it becomess an official feature\n@@@\n\n@@@ note { title=\"TLS 1.3 support\" }\nOtoroshi does support TLS 1.3 when used in combination with JDK 11\n\n\n@@@\n\n@@@ div { .centered-img }\n\n@@@\n\nHere you can add your own certificates, your own CA and even create self signed certificates or certificates from CAs. You can enable auto renewal of thoses self signed certificates or certificates generated. Certificates have to be created with the certificate chain and the private key in PEM format with no password on the private key.\n\nYou can remove the password of a key with the following command\n\n```sh\nopenssl rsa -in keywithpassword.key -out keywithoutpassword.key\n```\n\n@@@ div { .centered-img }\n\n@@@\n\n"},{"name":"1-groups.md","id":"/usage/1-groups.md","url":"/usage/1-groups.html","title":"Managing service groups","content":"# Managing service groups\n\nGo to `settings (cog icon) / All service groups` to access the list of service groups.\n\n@@@ div { .centered-img }\n\n@@@\n\nAnd you should see the list of existing `Service groups`.\n\n@@@ div { .centered-img }\n\n@@@\n\nBut what is a `Service group` anyway ?\n\n## Otoroshi entities\n\nThere are 3 major entities at the core of Otoroshi :\n\n* **service groups**\n* service descriptors\n* api keys\n\n@@@ div { .centered-img }\n\n@@@\n\nA `service group` is just some kind of logical container for `service descriptors`. A `service group` also has some `api keys` assigned that will be used to access all the `service descriptors` contained in the `service group`.\n\n## Create a service group\n\nA `service group` is a really simple structure with an `id`, a name and a description. To create a new one, just click on the `Add item` button.\n\n@@@ div { .centered-img }\n\n@@@\n\nmodify the name and the description of the group\n\n@@@ div { .centered-img }\n\n@@@\n\nand click on `Create group`\n\n@@@ div { .centered-img }\n\n@@@\n\nThen, you should find your brand new `Service group` in the list of `Service groups`\n\n@@@ div { .centered-img }\n\n@@@\n\n## Update a service\n\nTo update a `Service group`, just click on the edit button of your `Service group`\n\n@@@ div { .centered-img }\n\n@@@\n\nUpdate the name and description of the `Service group` and click on the `Update group` button to validate name update.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Delete a service group\n\nTo delete a `Service group`, just click on the delete button of your `Service group`\n\n@@@ div { .centered-img }\n\n@@@\n\nFinally confirm the command\n\n@@@ div { .centered-img }\n\n@@@\n"},{"name":"2-services.md","id":"/usage/2-services.md","url":"/usage/2-services.html","title":"Managing services","content":"# Managing services\n\nNow let's create services. Services or `service descriptor` let you declare how to proxy a call from a domain name to another domain name (or multiple domain names). Let's say you have an API exposed on `http://192.168.0.42` and I want to expose it on `https://my.api.foo`. Otoroshi will proxy all calls to `https://my.api.foo` and forward them to `http://192.168.0.42`. While doing that, it will also log everyhting, control accesses, etc.\n\n## Otoroshi entities\n\nThere are 3 major entities at the core of Otoroshi\n\n* service groups\n* **service descriptors**\n* api keys\n\n@@@ div { .centered-img }\n\n@@@\n\nA `service descriptor` is contained in a `service group` and is allowed to be accessed by all the `api key`s authorized on the `service group`.\n\n## Create a service descriptor\n\nTo create a `service descriptor`, click on `Add service` on the Otoroshi sidebar. Then you will be asked to choose a name for the service what is the group of the service. You also have two buttons to create a new group and assign it to the service and create a new group with a name based on the service name.\n\nYou will have a serie of toggle buttons to\n\n* activate / deactivate a service\n* display maintenance page for a service\n* display contruction page for a service\n* enable otoroshi custom response headers containing request id, latency, etc \n* force https usage on the exposed service\n\nThen, you will be able to choose the URL that will be used to reach your new service on Otoroshi.\n\n@@@ div { .centered-img #service-flags }\n\n@@@\n\nIn the `service targets` section, you will be able to choose where the call will be forwarded. You can use multiple targets, in that case, Otoroshi will perform a round robin load balancing between the targets. If the `override Host header` toggle is one, the host header will be changed for the host of the target. For example, if you request `http://www.oto.tools/api` with a target to `http://www-internal.service.local/api`, the target will receive a `Host: www-internal.service.local` instead of `Host: www.oto.tools`.\n\nYou can also specify a target root, if you say that the target root is `/foo/`, then any call to `https://my.api.foo` will call `http://192.168.0.42/foo/` and nay call to `https://my.api.foo/bar` will call `http://192.168.0.42/foo/bar`.\n\nIn the URL patterns section, you will be able to choose, URL by URL which is private and which is public. By default, all services are private and each call must provide an `api key`. But sometimes, you need to access a service publicly. In that case, you can provide patterns (regex) to make some or all URL public (for example with the pattern `/.*`). You also have a `private pattern` field to restrict public patterns.\n\n@@@ div { .centered-img #targets }\n\n@@@\n\n### Otoroshi exchange protocol\n\nIf you enable secure communication for a given service, you will have to add a filter on the target application that will take the `Otoroshi-State` header and return it in a header named `Otoroshi-State-Resp`. Otoroshi is also sending a `JWT token`in a header named `Otoroshi-Claim` that the target app can validate.\n\n@@@ div { .centered-img }\n\n@@@\n\nThe `Otoroshi-Claim` is a JWT token containing some informations about the service that is called and the client if available. \n\nBy default, the otoroshi jwt token is signed with the `app.claim.sharedKey` config property (or using the `$CLAIM_SHAREDKEY` env. variable) and uses the `HMAC512` signing algorythm. But it is possible to customize how the token is signed from the service descriptor page in the `Otoroshi exchange protocol` section. \n\n@@@ div { .centered-img }\n\n@@@\n\nusing another signing algo.\n\n@@@ div { .centered-img }\n\n@@@\n\nhere you can choose the signing algorithm and the secret/keys used. You can use syntax like `${env.MY_ENV_VAR}` or `${config.my.config.path}` to provide secret/keys values. \n\nFor example, for a service named `my-service` with a signing key `secret` with `HMAC512` signing algorythm, the basic JWT token that will be sent should look like the following\n\n```\neyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiItLSIsImF1ZCI6Im15LXNlcnZpY2UiLCJpc3MiOiJPdG9yb3NoaSIsImV4cCI6MTUyMTQ0OTkwNiwiaWF0IjoxNTIxNDQ5ODc2LCJqdGkiOiI3MTAyNWNjMTktMmFjNy00Yjk3LTljYzctMWM0ODEzYmM1OTI0In0.mRcfuFVFPLUV1FWHyL6rLHIJIu0KEpBkKQCk5xh-_cBt9cb6uD6enynDU0H1X2VpW5-bFxWCy4U4V78CbAQv4g\n```\n\nif you decode it, the payload will look something like\n\n```json\n{\n \"sub\": \"apikey_client_id\",\n \"aud\": \"my-service\",\n \"iss\": \"Otoroshi\",\n \"exp\": 1521449906,\n \"iat\": 1521449876,\n \"jti\": \"71025cc19-2ac7-4b97-9cc7-1c4813bc5924\"\n}\n```\n\nIf you want to validate the `Otoroshi-Claim` on the target app side to ensure that the input requests only comes from `Otoroshi`, you will have to write an HTTP filter to do the job. For instance, if you want to write a filter to make sure that requests only comes from Otoroshi, you can write something like the following (using playframework 2.6).\n\nScala\n: @@snip [filter.scala](../snippets/filter.scala)\n\nJava\n: @@snip [filter.java](../snippets/filter.java)\n\n\n### Canary mode\n\nOtoroshi provides a feature called `Canary mode`. It lets you define new targets for a service, and route a percentage of the traffic on those targets. It's a good way to test a new version of a service before public release. As any client need to be routed to the same version of targets any time, Otoroshi will issue a special header and a cookie containing a `session id`. The header is named `Otoroshi-Canary-Id`.\n\n@@@ div { .centered-img }\n\n@@@\n\n### Service health check\n\nOtoroshi is also capable of checking the health of a service. You can define a URL that will be tested, and Otoroshi will ping that URL regularly. Will doing so, Otoroshi will pass a numeric value in a header named `Otoroshi-Health-Check-Logic-Test`. You can respond with a header named `Otoroshi-Health-Check-Logic-Test-Result` that contains the value of `Otoroshi-Health-Check-Logic-Test` + 42 to indicate that the service is working properly.\n\n@@@ div { .centered-img }\n\n@@@\n\n### Service circuit breaker\n\nIn Otoroshi, each service has its own client settings with a circuit breaker and some retry capabilities. In the `Client settings` section, you will be able to customize the client's behavior.\n\n@@@ div { .centered-img }\n\n@@@\n\n### Service settings\n\nYou can also provide some additionnal information about a given service, like an `Open API` descriptor, some metadata, a list of whitelisted/blacklisted ip addresses, etc.\n\nHere you can also define some headers that will be added to each request to the targets. And you will be able to define headers to route the call only if the defined header is present on the request.\n\n@@@ div { .centered-img #service-meta }\n\n@@@\n\n### Read only\n\nThe read only flag on a service descriptor means that this service can only be used with `HEAD`, `OPTIONS` and `GET` http verbs. You can also active the same flag on `ApiKey`s to be more specific on who cannot use write http verbs.\n\n### CORS \n\nIf you enabled this section, CORS will be automatically supported on the current service provider. The pre-flight request will be handled by Otoroshi. You can customize every CORS headers :\n\n@@@ div { .centered-img }\n\n@@@\n\n### Service authentication\n\nSee @ref:[Aauthentication](./9-auth.md)\n\n### Custom error templates\n\nFinally, you can define custom error templates that will be displayed when an error occurs when Otoroshi try to reach the target or when Otoroshi itself has an error. You can also define custom templates for maintenance and service pages.\n"},{"name":"3-apikeys.md","id":"/usage/3-apikeys.md","url":"/usage/3-apikeys.html","title":"Managing API keys","content":"# Managing API keys\n\nNow that you know how to create service groups and service descriptors, we will see how to create API keys.\n\n## Otoroshi entities\n\nThere are 3 major entities at the core of Otoroshi.\n\n* service groups\n* service descriptors\n* **api keys**\n\n@@@ div { .centered-img }\n\n@@@\n\nAn `API key` related to a `service group` to allow you to access any `service descriptor` contained in a `service group`. You can, of course, create multiple `API key` for a given `service group`.\n\nIn the Otoroshi admin dashboard, we chose to access `API keys` from `service descriptors` only, but when you access `API keys` for a `service descriptor`, you actually access `API keys` for the `service group` containing the `service descriptor`.\n\n`API keys` can be provided to Otoroshi through :\n\n* `Otoroshi-Authorization: Basic $base64(client_id:client_secret)` header, in that case, the `Otoroshi-Authorization` header will **not** be sent to the target. `Basic ` is optional.\n* `Authorization: Basic $base64(client_id:client_secret)` header, in that case, the `Authorization` header **will** be sent to the target\n* `Otoroshi-Token: Bearer $jwt_token` where the JWT token has been signed with the `API key` client secret, in that case, the `Otoroshi-Token` header will **not** be sent to the target. `Bearer ` is optional.\n* `Authorization: Bearer $jwt_token` where the JWT token has been signed with the `API key` client secret, in that case, the `Authorization` header **will** be sent to the target\n* `Cookie: access_token=$jwt_token;` where the JWT token has been signed with the `API key` client secret, in that case, the cookie named `access_token` **will** be sent to the target\n* `Otoroshi-Client-Id` and `Otoroshi-Client-Secret` headers, in that case the `Otoroshi-Client-Id` and `Otoroshi-Client-Secret` headers will not be sent to the target.\n\n## List API keys for a service descriptor\n\nGo to a service descriptor using `All services` quick link in the sidebar or the search box.\n\n@@@ div { .centered-img }\n\n@@@\n\nSelect a `service descriptor`.\n\n@@@ div { .centered-img }\n\n@@@\n\nClick on `API keys` in the sidebar\n\n@@@ div { .centered-img }\n\n@@@\n\nYou should see the list of API keys for that `service descriptor`\n\n@@@ div { .centered-img }\n\n@@@\n\n## Create an API key for a service descriptor\n\n@@@ div { .centered-img }\n\n@@@\n\nYou can add a name for your new API key, you can also change client's id and client's secret. You can also configure the throttling rate of the API key (calls per second), and the authorized number of call per day and per month. You may also activate or de-activate the api key from that screen.\n\nInformations about current quotas usage will be returned in response headers.\n\n* `Otoroshi-Daily-Calls-Remaining` : authorized calls remaining for this day\n* `Otoroshi-Monthly-Calls-Remaining` : authorized calls remaining for this month\n* `Otoroshi-Proxy-Latency` : latency induced by Otoroshi\n* `Otoroshi-Upstream-Latency` : latency between Otoroshi and target\n\n@@@ div { .centered-img #quotas }\n\n@@@\n\n@@@ warning\nDaily and monthly quotas are based on the following rules :\n\n* daily quota is computed between 00h00:00.000 and 23h59:59.999\n* monthly qutoas is computed between the first day of the month at 00h00:00.000 and the last day of the month at 23h59:59.999\n@@@\n\n## Update an API key\n\nTo update an `API key`, just click on the edit button of your `API key`\n\n@@@ div { .centered-img }\n\n@@@\n\nUpdate the name, secret, state and quotas (if needed) of the `API key` and click on the `Update API key` button\n\n@@@ div { .centered-img }\n\n@@@\n\n## Delete an API key\n\nTo delete an `API key`, just click on the delete button of your `API key`\n\n@@@ div { .centered-img }\n\n@@@\n\nand confirm the command\n\n@@@ div { .centered-img }\n\n@@@\n\n### Read only\n\nThe read only flag on an `ApiKey` this apikey can only use allowed services with `HEAD`, `OPTIONS` and `GET` http verbs.\n\n## Use a JWT token to pass an API key\n\nYou can use a JWT token to pass an API key to Otoroshi. \nYou can use `Otoroshi-Authorization: Bearer $jwt_token`, `Authorization: Bearer $jwt_token` header and `Cookie: access_token=$jwt_token;` to pass the JWT token.\nYou have to create a JWT token with a signing algorythm that can be `HS256` or `HS512`. Then you have to provide an `iss` claim with the value of your API key `clientId` and sign the JWT token with your API key `clientSecret`.\n\nFor example, with an API key like `clientId=abcdef` and `clientSecret=1234456789`, your JWT token should look like\n\n```json\n{\n \"alg\": \"HS256\",\n \"typ\": \"JWT\"\n}\n{\n \"iss\":\"abcdef\",\n \"name\": \"John Doe\",\n \"admin\": true\n}\n```\n\nin that case, when you sign the token with the secret of the API key `1234456789`, the signature will be `_eancnYCD3makSSox2v2xErjNYkRtcX558QiJGCbino`, resulting in a encoded JWT header like\n\n```\neyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.\neyJpc3MiOiJhYmNkZWYiLCJuYW1lIjoiSm9obiBEb2UiLCJhZG1pbiI6dHJ1ZX0.\n_eancnYCD3makSSox2v2xErjNYkRtcX558QiJGCbino\n```\n"},{"name":"4-monitor.md","id":"/usage/4-monitor.md","url":"/usage/4-monitor.html","title":"Monitoring services","content":"# Monitoring services\n\nOnce you have declared services, you can monitor them with Otoroshi.\n\n@@@ warning\nYou have to use [Elastic](https://www.elastic.co) to enable analytics features in Otoroshi\n@@@\n\nOnce you have setup @ref:[Otoroshi events push to an elastic cluster](../integrations/analytics.md) (through webhooks, kafka, or elastic integration) you can setup Otoroshi events read from an elastic cluster. Go to `settings (cog icon) / Danger Zone` and expand the `Analytics: Elastic dashboard datasource (read)` section.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Service healthcheck\n\nIf you have defined an health check URL in the service descriptor, you can access the health check page from the sidebar of the service page.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Service live stats\n\nYou can also monitor live stats like total of served request, average response time, average overhead, etc. The live stats page can be accessed from the sidebar of the service page.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Service analytics\n\nYou can also get some aggregated metrics. The analytics page can be accessed from the sidebar of the service page.\n\n@@@ div { .centered-img }\n\n@@@\n"},{"name":"5-sessions.md","id":"/usage/5-sessions.md","url":"/usage/5-sessions.html","title":"Managing sessions","content":"# Managing sessions\n\nWith Otoroshi you can manage sessions of connected users and you can discard sessions whenever you want. Session last 24h by default and you can customize them with `app.backoffice.session.exp` and `app.privateapps.session.exp` @ref:[config keys](../firstrun/configfile.md)\n\n## Admin. sessions\n\nTo see last current admin session on Otoroshi from the UI, go to `settings (cog icon) / Admins sessions`. Here you can discard individual sessions or all sessions at once using `Discard session` and `Discard all sessions` buttons.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Private apps. session\n\nTo see last current admin session on Otoroshi from the UI, go to `settings (cog icon) / Priv. apps sessions`. Here you can discard individual sessions or all sessions at once using `Discard session` and `Discard all sessions` buttons.\n\n@@@ div { .centered-img }\n\n@@@\n"},{"name":"6-audit.md","id":"/usage/6-audit.md","url":"/usage/6-audit.html","title":"Auditing Otoroshi","content":"# Auditing Otoroshi\n\nWith Otoroshi, any admin action and any sucpicious/alert action is recorded. These records are stored in Otoroshi's datastore (only the last n records, defined by the `app.events.maxSize` @ref:[config key](../firstrun/configfile.md)). All the records can be send through the analytics mechanism (WebHook, Kafka, Elastic) for external and/or further usage. We recommand sending away those records for security reasons.\n\n@@@ warning\nYou have to use [Elastic](https://www.elastic.co) to enable analytics features in Otoroshi. See @ref:[Elastic setup section](../integrations/analytics.md)\n@@@\n\n## Audit trail\n\nTo see last `app.events.maxSize` admin actions on Otoroshi from the UI, go to `settings (cog icon) / Audit log`.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Alerts\n\nTo see last `app.events.maxSize` alerts on Otoroshi from the UI, go to `settings (cog icon) / Alerts log`.\n\n@@@ div { .centered-img }\n\n@@@\n\n## List of possible alerts\n\n```\nMaxConcurrentRequestReachedAlert\nCircuitBreakerOpenedAlert\nCircuitBreakerClosedAlert\nSessionDiscardedAlert\nSessionsDiscardedAlert\nPanicModeAlert\nOtoroshiExportAlert\nU2FAdminDeletedAlert\nBlackListedBackOfficeUserAlert\nAdminLoggedInAlert\nAdminFirstLogin\nAdminLoggedOutAlert\nDbResetAlert\nDangerZoneAccessAlert\nGlobalConfigModification\nRevokedApiKeyUsageAlert\nServiceGroupCreatedAlert\nServiceGroupUpdatedAlert\nServiceGroupDeletedAlert\nServiceCreatedAlert\nServiceUpdatedAlert\nServiceDeletedAlert\nApiKeyCreatedAlert\nApiKeyUpdatedAlert\nApiKeyDeletedAlert\n```\n"},{"name":"7-metrics.md","id":"/usage/7-metrics.md","url":"/usage/7-metrics.html","title":"Otoroshi global metrics","content":"# Otoroshi global metrics\n\nOtoroshi provide some global metrics about services usage. Go to `settings (cog icon) / Global Ananlytics`\n\n@@@ warning\nYou have to use [Elastic](https://www.elastic.co) to enable analytics features in Otoroshi. See @ref:[Elastic setup section](../integrations/analytics.md)\n@@@\n\n@@@ div { .centered-img }\n\n@@@\n"},{"name":"8-importsexports.md","id":"/usage/8-importsexports.md","url":"/usage/8-importsexports.html","title":"Import and export","content":"# Import and export\n\nWith Otoroshi you can easily save the current state of the proxy and restore it later. Go to `settings (cog icon) / Danger Zone` and scroll to the bottom of the page\n\n## Full export\n\nClick on the `Full export` button.\n\n@@@ div { .centered-img }\n\n@@@\n\nYour browser will start to download a JSON file containing the internal state of your Otoroshi cluster.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Full import\n\nIf you want to restore an export, go to `settings (cog icon) / Danger Zone` and scroll to the bottom of the page. Click on the `Recover from full export file` button\n\n@@@ div { .centered-img }\n\n@@@\n\nChoose export file on your system.\n\n@@@ div { .centered-img }\n\n@@@\n\nClick on the `Flush datastore and import ...` button, confirm and you will be logged out.\n\n@@@ div { .centered-img }\n\n@@@\n"},{"name":"9-auth.md","id":"/usage/9-auth.md","url":"/usage/9-auth.html","title":"Authentication","content":"# Authentication\n\nYou can create auth. configuration in Otoroshi. Just go to `settings (cog icon) / Global auth. configs`.\n\n## OAuth 2\n\nCreate a new `Generic oauth2 provider` config and customize the following informations:\n\n```json\n{\n \"clientId\": \"xxxx\",\n \"clientSecret\": \"xxxx\",\n \"authorizeUrl\": \"http://yourOAuthServer/oauth/authorize\",\n \"tokenUrl\": \"http://yourOAuthServer/oauth/token\",\n \"userInfoUrl\": \"http://yourOAuthServer/userinfo\",\n \"loginUrl\": \"http://yourOAuthServer/login\",\n \"logoutUrl\": \"http://yourOAuthServer/logout?redirectQueryParamName=${redirect}\",\n \"accessTokenField\": \"access_token\",\n \"nameField\": \"name\",\n \"emailField\": \"email\",\n \"callbackUrl\": \"http://privateapps.oto.tools/privateapps/generic/callback\"\n}\n```\n\nIf used for BackOffice authentication, the callback url should be `http://otoroshi.oto.tools/backoffice/auth0/callback`.\n\nFor `logoutUrl`, `redirectQueryParamName` is a parameter with a name specific to your OAuth2 provider (for example, in Auth0, this parameter is called `returnTo`, in Kecloak it is called `redirect_uri`).\n\nif you are using a [KeyCloak](https://www.keycloak.org/) server, you can configure it this way, assuming you are using the master realm and you created a new client with a client secret, callback urls set to `http://privateapps.oto.tools/*`.\n\n```json\n{\n \"clientId\": \"clientId\",\n \"clientSecret\": \"clientSecret\",\n \"authorizeUrl\": \"http://keycloakHost/auth/realms/master/protocol/openid-connect/auth\",\n \"tokenUrl\": \"http://keycloakHost/auth/realms/master/protocol/openid-connect/token\",\n \"userInfoUrl\": \"http://keycloakHost/auth/realms/master/protocol/openid-connect/userinfo\",\n \"loginUrl\": \"http://keycloakHost/auth/realms/master/protocol/openid-connect/auth\",\n \"logoutUrl\": \"http://keycloakHost/auth/realms/master/protocol/openid-connect/logout?redirect_uri=${redirect}\",\n \"accessTokenField\": \"access_token\",\n \"nameField\": \"name\",\n \"emailField\": \"email\",\n \"callbackUrl\": \"http://privateapps.oto.tools/privateapps/generic/callback\"\n}\n```\n\n## Ldap\n\nCreate a new `Ldap auth. provider` config and customize the following informations:\n\n```json\n{\n \"serverUrl\": \"ldap://ldap.forumsys.com:389\",\n \"searchBase\": \"dc=example,dc=com\",\n \"groupFilter\": \"ou=chemists\",\n \"searchFilter\": \"(mail=${username})\",\n \"adminUsername\": \"cn=read-only-admin,dc=example,dc=com\",\n \"adminPassword\": \"password\",\n \"nameField\": \"cn\",\n \"emailField\": \"mail\"\n}\n```\n\n## In Memory\n\nCreate a new `In memory auth. provider` config and then you will be able to create new users. To set the password, just click on the `Set password` button. It will generate a BCrypt hash of the password you typed.\n\n## Auth0\n\nCreate a new OAuth 2 config and add the following informations:\n\n```json\n{\n \"clientId\": \"yourAuth0ClientId\",\n \"clientSecret\": \"yourAuth0ClientSecret\",\n \"authorizeUrl\": \"https://yourAuth0Domain/authorize\",\n \"tokenUrl\": \"https://yourAuth0Domain/oauth/token\",\n \"userInfoUrl\": \"https://yourAuth0Domain/userinfo\",\n \"loginUrl\": \"https://yourAuth0Domain/authorize\",\n \"logoutUrl\": \"https://yourAuth0Domain/v2/logout?returnTo=${redirect}\",\n \"accessTokenField\": \"access_token\",\n \"nameField\": \"name\",\n \"emailField\": \"email\",\n \"otoroshiDataField\": \"app_metadata | otoroshi_data\",\n \"callbackUrl\": \"http://privateapps.oto.tools/privateapps/generic/callback\"\n}\n```\n\nIf you enable Otoroshi exchange protocol, the JWT xill have the following fields (all optional)\n\n* `email`\n* `name`\n* `picture`\n* `user_id`\n* `given_name`\n* `family_name`\n* `gender`\n* `locale`\n* `nickname`\n\nIn Auth0, the metadata is a flat object placed in the `profile / http://yourdomain/app_metadata / otoroshi_data`. You might need to write an Auth0 rule to copy app metadata under `http://yourdomain/app_metadata`, the `http://yourdomain/app_metadata` value is a config property `app.appMeta`. The rule could be something like the following\n\n```js\nfunction (user, context, callback) {\n var namespace = 'http://yourdomain/';\n context.idToken[namespace + 'user_id'] = user.user_id;\n context.idToken[namespace + 'user_metadata'] = user.user_metadata;\n context.idToken[namespace + 'app_metadata'] = user.app_metadata;\n callback(null, user, context);\n}\n```"},{"name":"index.md","id":"/usage/index.md","url":"/usage/index.html","title":"Using Otoroshi","content":"# Using Otoroshi\n\nNow we will see how to use Otoroshi for basic tasks that will be useful for your day to day work with Otoroshi.\n\n@@@ index\n\n* [create group](./1-groups.md)\n* [create service](./2-services.md)\n* [create API Keys](./3-apikeys.md)\n* [monitor service](./4-monitor.md)\n* [sessions management](./5-sessions.md)\n* [Audit trail and alerts](./6-audit.md)\n* [Global metrics](./7-metrics.md)\n* [Exports and imports](./8-importsexports.md)\n* [Authentication](./9-auth.md)\n\n@@@\n"},{"name":"videos.md","id":"/videos.md","url":"/videos.html","title":"Video tutorials","content":"# Video tutorials\n\n## Create a service restricted by an api key\n\nhere you will learn how to create a simple service that will proxy an existing api and how to secure it using an api key.\n\n\n\n## Use CLI to create load balanced services with retry\n\nhere you will learn how to create a service using the Otoroshi CLI and how to use load balancing with retry features of Otoroshi.\n\n"}] \ No newline at end of file +[{"name":"about.md","id":"/about.md","url":"/about.html","title":"About Otoroshi","content":"# About Otoroshi\n\nAt the beginning of 2017, we had the need to create a new environment to be able to create new \"digital\" products very quickly in an agile fashion at MAIF. Naturally we turned to PaaS solutions and chose the excellent Clever-Cloud product to run our apps. \n\nWe also chose that every feature team will have the freedom to choose its own technological stack to build its product. It was a nice move but it has also introduced some challenges in terms of homogeneity for traceability, security, logging, ... because we did not want to force library usage in the products. We could have used something like Service Mesh Pattern but the deployement model of Clever-Cloud prevented us to do it.\n\nThe right solution was to use a reverse proxy or some kind of API Gateway able to provide tracability, logging, security with apikeys, quotas, DNS as a service locator, etc. We needed something easy to use, with a human friendly UI, a nice API to extends its features, true hot reconfiguration, able to generate internal events for third party usage. A couple of solutions were available at that time, but not one seems to fit our needs, there was always something missing, too complicated for our needs or not playing well with Clever-Cloud deployment model.\n\nAt some point, we tried to write a small prototype to explore what could be our dream reverse proxy. The design was very simple, there were some rough edges but every major feature needed was there waiting to be enhanced.\n\n**Otoroshi** was born and we decided to move ahead with our hairy monster :)\n\n## Philosophy \n\nEvery OSS product build at MAIF like Izanami follow a common philosophy. \n\n* the services or API provided should be technology agnostic.\n* http first: http is the right answer to the previous quote \n* api First: The UI is just another client of the api. \n* secured: The services exposed need authentication for both humans or machines \n* event based: The services should expose a way to get notified of what happened inside. \n"},{"name":"api.md","id":"/api.md","url":"/api.html","title":"Admin REST API","content":"# Admin REST API\n\nOtoroshi provides a fully featured REST admin API to perform almost every operation possible in the Otoroshi dashboard. The Otoroshi dashbaord is just a regular consumer of the admin API.\n\nUsing the admin API, you can do whatever you want and enhance your Otoroshi instances with a lot of features that will feet your needs.\n\nOtoroshi also provides some connectors that uses the Otoroshi admin API to automate Otorshi's instances when used with stuff like containers orchestrators. For more informations about that, just go to the @ref:[third party integrations chapter](./integrations/index.md)\n\n## Swagger descriptor\n\nThe Otoroshi admin API is described using OpenAPI format and is available at :\n\nhttps://maif.github.io/otoroshi/manual/code/swagger.json\n\nEvery Otoroshi instance provides its own embedded OpenAPI descriptor at :\n\nhttp://otoroshi.oto.tools:8080/api/swagger.json\n\n## Swagger documentation\n\nYou can read the OpenAPI descriptor in a more human friendly fashion using `Swagger UI`. The swagger UI documentation of the Otoroshi admin API is available at :\n\nhttps://maif.github.io/otoroshi/swagger-ui/index.html\n\nEvery Otoroshi instance provides its own embedded OpenAPI descriptor at :\n\nhttp://otoroshi.oto.tools:8080/api/swagger/ui\n\nYou can also read the swagger UI documentation of the Otoroshi admin API below :\n\n@@@ div { .swagger-frame }\n\n\n@@@\n"},{"name":"archi.md","id":"/archi.md","url":"/archi.html","title":"Architecture","content":"# Architecture\n\nWhen we started the development of Otoroshi, we had several classical patterns in mind like `Service gateway`, `Service locator`, `Circuit breakers`, etc ...\n\nAt start we thought about providing a bunch of librairies that would be included in each microservice or app to perform these tasks. But the more we were thinking about it, the more it was feeling weird, unagile, etc, it also prevented us to use any technical stack we wanted to use. So we decided to change our approach to something more universal.\n\nWe chose to make Otoroshi the central part of our microservices system, something between a reverse-proxy, a service gateway and a service locator where each call to a microservice (even from another microservice) must pass through Otoroshi. There are multiple benefits to do that, each call can be logged, audited, monitored, integrated with a circuit breaker, etc without imposing libraries and technical stack. Any service is exposed through its own domain and we rely only on DNS to handle the service location part. Any access to a service is secured by default with an api key and is supervised by a circuit breaker to avoid cascading failures.\n\n@@@ div { .centered-img }\n\n@@@\n\nOtoroshi tries to embrace our @ref:[global philosophy](./about.md#philosophy) by providing a full featured REST admin api, a gorgeous admin dashboard written in [React](https://reactjs.org/) that uses the api, by generating traffic events, alerts events, audit events that can be consumed by several channels. Otoroshi also supports a bunch of datastores to better match with different use cases.\n\n@@@ div { .centered-img }\n\n@@@\n"},{"name":"cli.md","id":"/cli.md","url":"/cli.html","title":"Rust CLI","content":"# Rust CLI\n\n@@@ warning\nOtoroshi had a CLI written in Rust until v1.4.2 but it's deprecated now. If you are interested in maintaining this tool, just tell us ;)\n@@@\n"},{"name":"clevercloud.md","id":"/connectors/clevercloud.md","url":"/connectors/clevercloud.html","title":"Clever Cloud *","content":"# Clever Cloud *\n\n@@@ warning\nThis is an experimental connector\n@@@\n\nThe documentation is not ready yet as it's an experimental connector, but you can read more about it [here](https://github.com/MAIF/otoroshi/tree/master/connectors/clevercloud).\n"},{"name":"index.md","id":"/connectors/index.md","url":"/connectors/index.html","title":"Connectors","content":"# Connectors\n\nOtoroshi provides a set of connectors to be used in some use cases that could be useful :\n\n* [rancher connector](https://github.com/MAIF/otoroshi/tree/master/connectors/rancher) : a connector to synchronize a rancher cluster with Otoroshi on the fly\n* [kubernetes connector](https://github.com/MAIF/otoroshi/tree/master/connectors/kubernetes) : a connector to synchronize a kubernetes cluster with Otoroshi on the fly\n* [Clever-Cloud connector](https://github.com/MAIF/otoroshi/tree/master/connectors/clevercloud) : a connector to synchronize a Clever-Cloud organization with Otoroshi on the fly\n\nIf you want to build your own connector, Otoroshi provides a common library for synchronization. You can find the code here :\n\nhttps://github.com/MAIF/otoroshi/tree/master/connectors/common\n\n@@@ index\n\n* [clevercloud](./clevercloud.md)\n* [kubernetes](./kubernetes.md)\n* [rancher](./rancher.md)\n\n@@@\n"},{"name":"kubernetes.md","id":"/connectors/kubernetes.md","url":"/connectors/kubernetes.html","title":"Kubernetes *","content":"# Kubernetes *\n\n@@@ warning\nThis is an experimental connector\n@@@\n\nThe documentation is not ready yet as it's an experimental connector, but you can read more about it [here](https://github.com/MAIF/otoroshi/tree/master/connectors/kubernetes).\n"},{"name":"rancher.md","id":"/connectors/rancher.md","url":"/connectors/rancher.html","title":"Rancher *","content":"# Rancher *\n\n@@@ warning\nThis is an experimental connector\n@@@\n\nThe documentation is not ready yet as it's an experimental connector, but you can read more about it [here](https://github.com/MAIF/otoroshi/tree/master/connectors/rancher).\n"},{"name":"aws-beanstalk.md","id":"/deploy/aws-beanstalk.md","url":"/deploy/aws-beanstalk.html","title":"AWS - Elastic Beanstalk","content":"# AWS - Elastic Beanstalk\n\nNow you want to use Otoroshi on AWS. There are multiple options to deploy Otoroshi on AWS, \nfor instance :\n\n* You can deploy the [Docker image](../getotoroshi/fromdocker.md) on [Amazon ECS](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html)\n* You can create a basic [Amazon EC2](https://docs.aws.amazon.com/fr_fr/AWSEC2/latest/UserGuide/concepts.html), access it via SSH, then \ndeploy the @ref:[otoroshi.jar](../firstrun/run.md#from-jar-file) \n* Or you can use [AWS Elastic Beanstalk](https://aws.amazon.com/fr/elasticbeanstalk)\n\nIn this section we are going to cover how to deploy Otoroshi on [AWS Elastic Beanstalk](https://aws.amazon.com/fr/elasticbeanstalk). \n\n## AWS Elastic Beanstalk Overview\nUnlike Clever Cloud, to deploy an application on AWS Elastic Beanstalk, you don't link your app to your VCS repository, push your code and expect it to be built and run.\n\nAWS Elastic Beanstalk does only the run part. So you have to handle your own build pipeline, upload a Zip file containing your runnable, then AWS Elastic Beanstalk will take it from there. \n \nEg: for apps running on the JVM (Scala/Java/Kotlin) a Zip with the jar inside would suffice, for apps running in a Docker container, a Zip with the DockerFile would be enough. \n\n\n## Prepare your deployment target\nActually, there are 2 options to build your target. \n\nEither you create a DockerFile from this [Docker image](../getotoroshi/fromdocker.md), build a zip, and do all the Otoroshi custom configuration using ENVs.\n\nOr you download the @ref:[otoroshi.jar](../getotoroshi/frombinaries.md), do all the Otoroshi custom configuration using your own otoroshi.conf, and create a DockerFile that runs the jar using your otoroshi.conf. \n\nFor the second option your DockerFile would look like this :\n\n```dockerfile\nFROM openjdk:8\nVOLUME /tmp\nEXPOSE 8080\nADD otoroshi.jar otoroshi.jar\nADD otoroshi.conf otoroshi.conf\nRUN sh -c 'touch /otoroshi.jar'\nENV JAVA_OPTS=\"\"\nENTRYPOINT [ \"sh\", \"-c\", \"java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -Dconfig.file=/otoroshi.conf -jar /otoroshi.jar\" ]\n``` \n \nI'd recommend the second option.\n \nNow Zip your target (Jar + Conf + DockerFile) and get ready for deployment. \n\n## Create an Otoroshi instance on AWS Elastic Beanstalk\nFirst, go to [AWS Elastic Beanstalk Console](https://eu-west-3.console.aws.amazon.com/elasticbeanstalk/home?region=eu-west-3#/welcome), don't forget to sign in and make sure that you are in the good region (eg : eu-west-3 for Paris).\n\nHit **Get started** \n\n@@@ div { .centered-img }\n\n@@@\n\nSpecify the **Application name** of your application, Otoroshi for example.\n\n@@@ div { .centered-img }\n\n@@@\n \nChoose the **Platform** of the application you want to create, in your case use Docker.\n\nFor **Application code** choose **Upload your code** then hit **Upload**.\n\n@@@ div { .centered-img }\n\n@@@\n\nBrowse the zip created in the [previous section](#prepare-your-deployment-target) from your machine. \n\nAs you can see in the image above, you can also choose an S3 location, you can imagine that at the end of your build pipeline you upload your Zip to S3, and then get it from there (I wouldn't recommend that though).\n \nWhen the upload is done, hit **Configure more options**.\n \n@@@ div { .centered-img }\n\n@@@ \n \nRight now an AWS Elastic Beanstalk application has been created, and by default an environment named Otoroshi-env is being created as well.\n\nAWS Elastic Beanstalk can manage multiple environments of the same application, for instance environments can be (prod, preprod, expriments...). \n\nOtoroshi is a bit particular, it doesn't make much sense to have multiple environments, since Otoroshi will handle all the requests from/to downstream services regardless of the environment. \n \nAs you see in the image above, we are now configuring the Otoroshi-env, the one and only environment of Otoroshi.\n \nFor **Configuration presets**, choose custom configuration, now you have a load balancer for your environment with the capacity of at least one instance and at most four.\nI'd recommend at least 2 instances, to change that, on the **Capacity** card hit **Modify**. \n\n@@@ div { .centered-img }\n\n@@@\n\nChange the **Instances** to min 2, max 4 then hit **Save**. For the **Scaling triggers**, I'd keep the default values, but know that you can edit the capacity config any time you want, it only costs a redeploy, which will be done automatically by the way.\n \nInstances size is by default t2.micro, which is a bit small for running Otoroshi, I'd recommend a t2.medium. \nOn the **Instances** card hit **Modify**.\n\n@@@ div { .centered-img }\n\n@@@\n\nFor **Instance type** choose t2.medium, then hit **Save**, no need to change the volume size, unless you have a lot of http call faults, which means a lot more logs, in that case the default volume size may not be enough.\n\nThe default environment created for Otoroshi, for instance Otoroshi-env, is a web server environment which fits in your case, but the thing is that on AWS Elastic Beanstalk by default a web server environment for a docker-based application, runs behind an Nginx proxy.\nWe have to remove that proxy. So on the **Software** card hit **Modify**.\n \n@@@ div { .centered-img }\n\n@@@ \n \nFor **Proxy server** choose None then hit **Save**.\n\nAlso note that you can set Envs for Otoroshi in same page (see image below). \n\n@@@ div { .centered-img }\n\n@@@ \n\nTo finalise the creation process, hit **Create app** on the bottom right.\n\nThe Otoroshi app is now created, and it's running which is cool, but we still don't have neither a **datastore** nor **https**.\n \n## Create an Otoroshi datastore on AWS ElastiCache\n\nBy default Otoroshi uses non persistent memory to store it's data, Otoroshi supports many kinds of datastores. In this section we will be covering Redis datastore. \n\nBefore starting, using a datastore hosted by AWS is not at all mandatory, feel free to use your own if you like, but if you want to learn more about ElastiCache, this section may interest you, otherwise you can skip it.\n\nGo to [AWS ElastiCache](https://eu-west-3.console.aws.amazon.com/elasticache/home?region=eu-west-3#) and hit **Get Started Now**.\n\n@@@ div { .centered-img }\n\n@@@ \n\nFor **Cluster engine** keep Redis.\n\nChoose a **Name** for your datastore, for instance otoroshi-datastore.\n\nYou can keep all the other default values and hit **Create** on the bottom right of the page.\n\nOnce your Redis Cluster is created, it would look like the image below.\n\n@@@ div { .centered-img }\n\n@@@ \n\n\nFor applications in the same security group as your cluster, redis cluster is accessible via the **Primary Endpoint**. Don't worry the default security group is fine, you don't need any configuration to access the cluster from Otoroshi.\n\nTo make Otoroshi use the created cluster, you can either use Envs `APP_STORAGE=redis`, `REDIS_HOST` and `REDIS_PORT`, or set `app.storage=redis`, `app.redis.host` and `app.redis.port` in your otoroshi.conf.\n\n## Create SSL certificate and configure your domain\n\nOtoroshi has now a datastore, but not yet ready for use. \n\nIn order to get it ready you need to :\n\n* Configure Otoroshi with your domain \n* Create a wildcard SSL certificate for your domain\n* Configure Otoroshi AWS Elastic Beanstalk instance with the SSL certificate \n* Configure your DNS to redirect all traffic on your domain to Otoroshi \n \n### Configure Otoroshi with your domain\n\nYou can use ENVs or you can use a custom otoroshi.conf in your Docker container.\n\nFor the second option your otoroshi.conf would look like this :\n\n``` \n include \"application.conf\"\n http.port = 8080\n app {\n env = \"prod\"\n domain = \"mysubdomain.oto.tools\"\n rootScheme = \"https\"\n snowflake {\n seed = 0\n }\n events {\n maxSize = 1000\n }\n backoffice {\n subdomain = \"otoroshi\"\n session {\n exp = 86400000\n }\n }\n \n storage = \"redis\"\n redis {\n host=\"myredishost\"\n port=myredisport\n }\n \n privateapps {\n subdomain = \"privateapps\"\n }\n \n adminapi {\n targetSubdomain = \"otoroshi-admin-internal-api\"\n exposedSubdomain = \"otoroshi-api\"\n defaultValues {\n backOfficeGroupId = \"admin-api-group\"\n backOfficeApiKeyClientId = \"admin-client-id\"\n backOfficeApiKeyClientSecret = \"admin-client-secret\"\n backOfficeServiceId = \"admin-api-service\"\n }\n proxy {\n https = true\n local = false\n }\n }\n claim {\n sharedKey = \"myclaimsharedkey\"\n }\n }\n \n play.http {\n session {\n secure = false\n httpOnly = true\n maxAge = 2147483646\n domain = \".mysubdomain.oto.tools\"\n cookieName = \"oto-sess\"\n }\n }\n``` \n\n### Create a wildcard SSL certificate for your domain\n\nGo to [AWS Certificate Manager](https://eu-west-3.console.aws.amazon.com/acm/home?region=eu-west-3#/firstrun).\n\nBelow **Provision certificates** hit **Get started**.\n\n@@@ div { .centered-img }\n\n@@@ \n \nKeep the default selected value **Request a public certificate** and hit **Request a certificate**.\n \n@@@ div { .centered-img }\n\n@@@ \n\nPut your **Domain name**, use *. for wildcard, for instance *\\*.mysubdomain.oto.tools*, then hit **Next**.\n\n@@@ div { .centered-img }\n\n@@@ \n\nYou can choose between **Email validation** and **DNS validation**, I'd recommend **DNS validation**, then hit **Review**. \n \n@@@ div { .centered-img }\n\n@@@ \n \nVerify that you did put the right **Domain name** then hit **Confirm and request**. \n\n@@@ div { .centered-img }\n\n@@@\n \nAs you see in the image above, to let Amazon do the validation you have to add the `CNAME` record to your DNS configuration. Normally this operation takes around one day.\n \n### Configure Otoroshi AWS Elastic Beanstalk instance with the SSL certificate \n\nOnce the certificate is validated, you need to modify the configuration of Otoroshi-env to add the SSL certificate for HTTPS. \nFor that you need to go to [AWS Elastic Beanstalk applications](https://eu-west-3.console.aws.amazon.com/elasticbeanstalk/home?region=eu-west-3#/applications),\nhit **Otoroshi-env**, then on the left side hit **Configuration**, then on the **Load balancer** card hit **Modify**.\n\n@@@ div { .centered-img }\n\n@@@\n\nIn the **Application Load Balancer** section hit **Add listener**.\n\n@@@ div { .centered-img }\n\n@@@\n\nFill the popup as the image above, then hit **Add**. \n\nYou should now be seeing something like this : \n \n@@@ div { .centered-img }\n\n@@@ \n \n \nMake sure that your listener is enabled, and on the bottom right of the page hit **Apply**.\n\nNow you have **https**, so let's use Otoroshi.\n\n### Configure your DNS to redirect all traffic on your domain to Otoroshi\n \nIt's actually pretty simple, you just need to add a `CNAME` record to your DNS configuration, that redirects *\\*.mysubdomain.oto.tools* to the DNS name of Otoroshi's load balancer.\n\nTo find the DNS name of Otoroshi's load balancer go to [AWS Ec2](https://eu-west-3.console.aws.amazon.com/ec2/v2/home?region=eu-west-3#LoadBalancers:tag:elasticbeanstalk:environment-name=Otoroshi-env;sort=loadBalancerName)\n\nYou would find something like this : \n \n@@@ div { .centered-img }\n\n@@@ \n\nThere is your DNS name, so add your `CNAME` record. \n \nOnce all these steps are done, the AWS Elastic Beanstalk Otoroshi instance, would now be handling all the requests on your domain. ;) \n"},{"name":"clevercloud.md","id":"/deploy/clevercloud.md","url":"/deploy/clevercloud.html","title":"Clever Cloud","content":"# Clever Cloud\n\nNow you want to use Otoroshi on Clever Cloud. Otoroshi has been designed and created to run on Clever Cloud and a lot of choices were made because of how Clever Cloud works.\n\n## Create an Otoroshi instance on CleverCloud\n\nFirst, fork our project template on Github at https://github.com/MAIF/otoroshi-jar-clevercloud-template.\n\nIf you want to customize the build script, edit `./clevercloud/build.sh`\n\nIf you want to customize the configuration @ref:[use env. variables](../firstrun/env.md), you can use [the example provided below](#example-of-clevercloud-env-variables)\n\nCreate a new CleverCloud app based on your fork.\n\n@@@ div { .centered-img }\n\n@@@\n\nThen choose what kind of app your want to create, for Otoroshi, choose `Java + Jar`\n\n@@@ div { .centered-img }\n\n@@@\n\nNext, set up choose instance size and auto-scalling. Otoroshi can run on small instances, especially if you just want to test it.\n\n@@@ div { .centered-img }\n\n@@@\n\nFinally, choose a name for your app\n\n@@@ div { .centered-img }\n\n@@@\n\nNow you just need to customize environnment variables and add the custom build script as pre buid hook :\n\n`CC_PRE_BUILD_HOOK=./clevercloud/build.sh`\n\nat this point, you can also add other env. variables to configure Otoroshi like in [the example provided below](#example-of-clevercloud-env-variables)\n\n@@@ div { .centered-img }\n\n@@@\n\nYou can also use expert mode :\n\n@@@ div { .centered-img }\n\n@@@\n\nNow, your app is ready, don't forget to add a custom domains name on the CleverCloud app matching the Otoroshi app domain. For instance if you used domain names in env. variables like `changeme`, `changeme-admin-internal-api`, `changeme-api` on the `cleverapps.io` domain, declare `changeme.cleverapps.io`, `changeme-api.cleverapps.io`, `changeme-admin-internal-api.cleverapps.io`.\n\nYou will find the login/password tuple for first login in the app. logs.\n\n## Build and deploy Otoroshi from its source code\n\nFirst, fork our project template on Github at https://github.com/MAIF/otoroshi-clevercloud-template.\n\nIf you want to customize the build script, edit `./clevercloud/build.sh`\n\nIf you want to customize the configuration file, edit `./clevercloud/prod.conf` or @ref:[use env. variables](../firstrun/env.md)\n\nCreate a new Clever Cloud app based on your fork.\n\n@@@ div { .centered-img }\n\n@@@\n\nThen, you need to choose what kind of app your want to create, for Otoroshi, choose `Java or Scala + Play 2`\n\n@@@ div { .centered-img }\n\n@@@\n\nThen, you will be asked to choose what kind of machine you want to use. `M` instances are a good choice but you can use a less powerful ones. You can also activate auto-scaling or multi-instances to provie high availibility.\n\n@@@ div { .centered-img }\n\n@@@\n\nThen choose a name for your app :\n\n@@@ div { .centered-img }\n\n@@@\n\nNow you just need to customize environnment variables and add the custom build script as pre build hook :\n\n`CC_PRE_BUILD_HOOK=./clevercloud/build.sh`\n\nat this point, you can also add other env. variables to configure Otoroshi like in [the example provided below](#example-of-clevercloud-env-variables)\n\n@@@ div { .centered-img }\n\n@@@\n\nYou can also use expert mode :\n\n@@@ div { .centered-img }\n\n@@@\n\nNow, your app is ready, don't forget to add a custom domains name on the CleverCloud app matching the Otoroshi app domain. For instance if you used domain names in env. variables like `changeme`, `changeme-admin-internal-api`, `changeme-api` on the `cleverapps.io` domain, declare `changeme.cleverapps.io`, `changeme-api.cleverapps.io`, `changeme-admin-internal-api.cleverapps.io`.\n\nYou will find the login/password tuple for first login in the app. logs.\n\n## Example of CleverCloud env. variables\n\nYou can add more env variables to customize your Otoroshi instance like the following. Use the expert mode to copy/paste all the values in one shot :\n\n```\nAPP_ENV=prod\nAPP_STORAGE=inmemory\nAPP_DOMAIN=cleverapps.io\nAPP_ROOT_SCHEME=https\nAPP_BACKOFFICE_SUBDOMAIN=changeme\nADMIN_API_TARGET_SUBDOMAIN=changeme-admin-internal-api\nADMIN_API_EXPOSED_SUBDOMAIN=changeme-api\nADMIN_API_GROUP=psIZ0hI6eAQ2vp7DQoFfdUSfdmamtlkbXwYCe9WQHGBZMO6o5Kn1r2VVSmI61IVX\nADMIN_API_CLIENT_ID=pWkwudAifrflg8Bh\nADMIN_API_CLIENT_SECRET=ip53UuY5BFiM3wXkVUhhYrVdbsDYsANCNdRMnW3pU4I268ylsF6xxkvusS6Wv4AW\nADMIN_API_SERVICE_ID=GVQUWMZHaEYr1tCTNe9CdXOVE4DQnu1VUAx7YyXDlo5XupY3laZlWUnGyDt1vfGx\nCACHE_DEPENDENCIES=true\nCC_PRE_BUILD_HOOK=./clevercloud/build.sh\nCLAIM_SHAREDKEY=Tx1uQXW11pLNlZ25S4A08Uf8HbWDPxZ3KGSSm0B1s90gRk10PNy4d1HKY4Dnvvv5\nENABLE_METRICS=true\nJAVA_VERSION=8\nPORT=8080\nPLAY_CRYPTO_SECRET=7rNFga4AComd6ey09W9PaHqllLmPHb8WHBhlRe9xjTHOPlN15BCeSQf610cmLU1w\nSESSION_SECURE_ONLY=true\nSESSION_MAX_AGE=259200000\nSESSION_DOMAIN=changeme.cleverapps.io\nSESSION_NAME=otoroshi-session\nUSER_AGENT=otoroshi\n```\n"},{"name":"index.md","id":"/deploy/index.md","url":"/deploy/index.html","title":"Deploy to production","content":"# Deploy to production\n\nNow it's time to deploy Otoroshi in production, in this chapter we will see what kind of things you can do.\n\n@@@ index\n\n* [Clever Cloud](./clevercloud.md)\n* [AWS - Elastic Beanstalk](./aws-beanstalk.md)\n* [others](./other.md) \n* [Scaling](./scaling.md) \n\n@@@"},{"name":"other.md","id":"/deploy/other.md","url":"/deploy/other.html","title":"Others","content":"# Others\n\nOtoroshi can run wherever you want, even on a raspberry pi (Cluster^^) ;)\n\nThis section is not finished yet. So, as Otoroshi is available as a @ref:[Docker image](../getotoroshi/fromdocker.md) that you can run on any Docker compatible cloud, just go ahead and use it on cloud provider until we have more detailed documentation.\n\n## Running Otoroshi on AWS Elastic Beanstalk\n\nSee the @ref:[dedicated page to run Otoroshi on AWS Elastic Beanstalk](./aws-beanstalk.md)\n\n## Running Otoroshi on Amazon Elastic Container Service\n\nDeploy the @ref:[Docker image](../firstrun/run.md#from-docker) using [Amazon ECS](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html)\n\n## Running Otoroshi on GCE\n\nDeploy the @ref:[Docker image](../firstrun/run.md#from-docker) using [Google Compute Engine container integration](https://cloud.google.com/compute/docs/containers/deploying-containers)\n\n## Running Otoroshi on Azure\n\nDeploy the @ref:[Docker image](../firstrun/run.md#from-docker) using [Azure Container Service](https://azure.microsoft.com/en-us/services/container-service/)\n\n## Running Otoroshi on Heroku\n\nDeploy the @ref:[Docker image](../firstrun/run.md#from-docker) using [Docker integration](https://devcenter.heroku.com/articles/container-registry-and-runtime)\n\n## Running Otoroshi on CloudFoundry\n\nDeploy the @ref:[Docker image](../firstrun/run.md#from-docker) using [Docker integration](https://docs.cloudfoundry.org/adminguide/docker.html)\n\n## Running Otoroshi on your own infrastructure\n\nAs Otoroshi is a [Play Framework](https://www.playframework.com) application, you can read the doc about putting a `Play` app in production.\n\nhttps://www.playframework.com/documentation/2.6.x/ProductionConfiguration\n\nDownload the latest @ref:[Otoroshi distribution](../getotoroshi/frombinaries.md), unzip it, customize it and run it.\n"},{"name":"scaling.md","id":"/deploy/scaling.md","url":"/deploy/scaling.html","title":"Scaling Otoroshi","content":"# Scaling Otoroshi\n\n## Using multiple instances with a front load balancer\n\nOtoroshi has been designed to work with multiple instances. If you already have an infrastructure using frontal load balancing, you just have to declare Otoroshi instances as the target of all domain names handled by Otoroshi\n\n## Using master / workers mode of Otoroshi\n\nYou can read everything about it in @ref:[the clustering section](../topics/clustering.md) of the documentation.\n\n## Using IPVS\n\nYou can use [IPVS](https://en.wikipedia.org/wiki/IP_Virtual_Server) to load balance layer 4 traffic directly from the Linux Kernel to multiple instances of Otoroshi. You can find example of configuration [here](http://www.linuxvirtualserver.org/VS-DRouting.html) \n\n## Using DNS Round Robin\n\nYou can use [DNS round robin technique](https://en.wikipedia.org/wiki/Round-robin_DNS) to declare multiple A records under the domain names handled by Otoroshi.\n\n## Using software L4/L7 load balancers\n\nYou can use software L4 load balancers like NGINX or HAProxy to load balance layer 4 traffic directly from the Linux Kernel to multiple instances of Otoroshi.\n\nNGINX L7\n: @@snip [nginx-http.conf](../snippets/nginx-http.conf) \n\nNGINX L4\n: @@snip [nginx-tcp.conf](../snippets/nginx-tcp.conf) \n\nHA Proxy L7\n: @@snip [haproxy-http.conf](../snippets/haproxy-http.conf) \n\nHA Proxy L4\n: @@snip [haproxy-tcp.conf](../snippets/haproxy-tcp.conf) \n\n## Using a custom TCP load balancer\n\nYou can also use any other TCP load balancer, from a hardware box to a small js file like\n\ntcp-proxy.js\n: @@snip [tcp-proxy.js](../snippets/tcp-proxy.js) \n\ntcp-proxy.rs\n: @@snip [tcp-proxy.rs](../snippets/proxy.rs) \n\n"},{"name":"embedding.md","id":"/embedding.md","url":"/embedding.html","title":"Embedding Otoroshi","content":"# Embedding Otoroshi\n\nOtoroshi provides an API to start Otoroshi instances programmatically from any JVM app.\n\n## Getting the dependencies\n\nYou can get the Otoroshi dependency from bintray\n\nSbt\n: @@snip [build.sbt](./snippets/build.sbt)\n\nGradle\n: @@snip [build.gradle](./snippets/build.gradle)\n\n## Starting Otoroshi\n\nNow just instanciate an Otoroshi proxy with the configuration you like and you will be able to control it using the internal APIs of Otoroshi.\n\nScala\n: @@snip [embed.scala](./snippets/embed.scala)\n\nJava\n: @@snip [embed.java](./snippets/embed.java)\n"},{"name":"features.md","id":"/features.md","url":"/features.html","title":"Features ","content":"# Features \n\nAll the features supported by **Otoroshi** are listed below\n\n* Dynamic changes at runtime without full reload \n* Can proxy any HTTP/HTTP2 server\n* Can proxy websockets\n* Full featured admin Rest Api to control Otoroshi the way you want. Included, Swagger descriptor\n* Gorgeous React Web UI\n* Full end-to-end streaming of HTTP requests and responses\n* Completely non blocking and async internals\n* @ref:[Official Docker image](./getotoroshi/fromdocker.md)\n* @ref:[Multi backend datastore support](./firstrun/datastore.md)\n * Redis\n * In memory\n * Cassandra (experimental support)\n * Mongo (experimental support)\n * LevelDB (not suitable for production usage)\t\t\n* @ref:[Service is private (Api key access) by default with exclusions](./usage/2-services.md)\n* @ref:[Support wildcard domain names per service](./usage/2-services.md)\n* @ref:[Support routing headers for a service (ie. for service versioning)](./usage/2-services.md#service-meta)\n* @ref:[Support adding headers for a service request (ie. add Authorization header)](./usage/2-services.md#service-meta)\n* @ref:[Support custom html errors templates per service](./usage/2-services.md#custom-error-templates)\n* @ref:[Configurable circuit breaker and retries (with backoff) on network errors per service](./usage/2-services.md#service-circuit-breaker)\n* @ref:[Round Robin load balancing per service](./usage/2-services.md#targets)\n* @ref:[Services can be declared private (private apps) using an Auth0 domain](./usage/2-services.md#service-flags)\n* @ref:[Support for canary mode per service](./usage/2-services.md#canary-mode)\n* @ref:[Configurable health check per service](./usage/2-services.md#service-health-check)\n* @ref:[Support IP addresses blacklist per service (with wildcard support)](./usage/2-services.md#service-settings)\n* @ref:[Support IP addresses whitelist per service (with wildcard support)](./usage/2-services.md#service-settings)\n* @ref:[Support mutiple Api keys per service](./usage/3-apikeys.md)\n* @ref:[Support configurable throttling quota per Api key](./usage/3-apikeys.md#quotas)\n* @ref:[Support configurable daily quota per Api key](./usage/3-apikeys.md#quotas)\n* @ref:[Support configurable monthly quota per Api key](./usage/3-apikeys.md#quotas)\n* @ref:[Api keys are authorized for a group of services](./usage/3-apikeys.md)\n* @ref:[Api keys can be passed with custom headers, `Authorization: Basic ` headers, `Authorization: Bearer` or Cookies](./usage/3-apikeys.md)\n* @ref:[Add current Api key quotas usage in response headers](./usage/3-apikeys.md#quotas)\n* @ref:[Add current latencies in response headers](./usage/3-apikeys.md#quotas)\n* @ref:[Support service duplication through web UI](./usage/2-services.md#service-flags)\n* @ref:[Maintenance page per service](./usage/2-services.md#service-flags)\n* @ref:[Build page per service](./usage/2-services.md#service-flags)\n* @ref:[Force HTTPS usage per service](./usage/2-services.md#service-flags)\n* @ref:[Force secured exchanges with exclusions per service](./usage/2-services.md#service-flags)\n* @ref:[OpenAPI documentation displayed through web UI per service](./usage/2-services.md#service-settings)\n* @ref:[Live metrics per service (Rest)](./usage/4-monitor.md#service-live-stats)\n* @ref:[Metrics and analytics per service (if used with the Elastic connector)](./usage/4-monitor.md#service-analytics)\n* @ref:[Global metrics and analytics (if used with the Elastic connector)](./usage/7-metrics.md)\n* @ref:[Global audit log on admins actions](./usage/6-audit.md#audit-trail)\n* @ref:[Global alert log on admins actions](./usage/6-audit.md#alerts)\n* @ref:[Internal events can be sent outside using webhooks or Kafka topic](./setup/dangerzone.md#analytics-settings)\n* @ref:[Audit events can be sent outside using webhooks or Kafka topic](./setup/dangerzone.md#analytics-settings)\n* @ref:[Alerts events can be sent outside using webhooks or Kafka topic](./setup/dangerzone.md#analytics-settings)\n* @ref:[Alerts can be send to people by email using Mailgun](./integrations/mailgun.md)\n* @ref:[Support global IP addresses blacklist (with wildcard support)](./setup/dangerzone.md#whitelist-blacklist-settings)\n* @ref:[Support global IP addresses whitelist (with wildcard support)](./setup/dangerzone.md#whitelist-blacklist-settings)\n* @ref:[Support global endless responses for IP addresses (128 Gb of 0 for each response)](./setup/dangerzone.md#whitelist-blacklist-settings)\n* @ref:[Support global throttling quota](./setup/dangerzone.md#global-throttling-settings)\n* @ref:[Support global throttling quota per IP address (with wildcard support)](./setup/dangerzone.md#global-throttling-settings)\n* @ref:[Support global max number of concurrent connections](./setup/dangerzone.md#commons-settings)\n* Support global request tracing\n* @ref:[Support full JSON export of the reverse proxy state](./usage/8-importsexports.md#full-export)\n* @ref:[Support full JSON import of the reverse proxy state](./usage/8-importsexports.md#full-import)\n* @ref:[Support initial internal state import from JSON local file](./firstrun/configfile.md#db-configuration)\n* @ref:[Support initial internal state import from JSON file over HTTP](./firstrun/configfile.md#db-configuration)\n* @ref:[Enforce incoming JWT token verification and transformation](./topics/jwt-verifications.md)\n* @ref:[Introduce HTTP level chaos engineering pratices with the Snow Monkey](./topics/snow-monkey.md)\n* @ref:[Native support for service mesh architectures](./topics/service-mesh.md)\n* @ref:[Global live metrics](./setup/index.md#first-login)\n* @ref:[Send metrics to a StatsD/Datadog agent](./integrations/statsd.md)\n* @ref:[Advanced CleverCloud integration (create services from CleverCloud apps)](./integrations/clevercloud.md)\n* @ref:[Support admin login with any auth. module](./usage/9-auth.md)\n* @ref:[Support admin login with FIDO U2F device](./setup/admin.md#create-admin-user-with-u2f-device-login)\n* @ref:[Dynamic SSL termination](./topics/ssl.md)\n* @ref:[Rust CLI running on MacOS, Linux and Windows](./cli.md)\n* @ref:[Connectors to various HTTP services providers](./connectors/index.md)\n * [generic connector API](https://github.com/MAIF/otoroshi/tree/master/connectors/common)\n * @ref:[Clever Cloud](./connectors/clevercloud.md)\n * @ref:[Rancher](./connectors/rancher.md)\n * @ref:[Kubernetes](./connectors/kubernetes.md)\n"},{"name":"configfile.md","id":"/firstrun/configfile.md","url":"/firstrun/configfile.html","title":"Config. with files","content":"# Config. with files\n\nThere is a lot of things you can configure in Otoroshi. By default, Otoroshi provides a configuration that should be enough for testing purpose. But you'll likely need to update this configuration when you'll need to move into production.\n\nIn this page, any configuration property can be set at runtime using a `-D` flag when launching Otoroshi like\n\n```sh\njava -Dhttp.port=8080 -jar otoroshi.jar\n```\n\nor\n\n```sh\n./bin/otoroshi -Dhttp.port=8080 \n```\n\n## Common configuration\n\n| name | type | default value | description |\n| ---- |:----:| -------------- | ----- |\n| `app.domain` | string | \"oto.tools\" | the domain on which Otoroshi UI/API is be exposed|\n| `app.rootScheme` | string | \"http\" | the scheme on which Otoroshi is exposed, either \"http\" or \"https\" |\n| `app.snowflake.seed` | number | 0 | this number will is used to generate unique ids across the cluster. Each Otorshi instance must have a unique seed. |\n| `app.events.maxSize` | number | 1000 | max number of analytic and alert events stored locally |\n| `app.backoffice.exposed` | boolean | true | does the current Otoroshi instance exposed a backoffice ui|\n| `app.backoffice.subdomain` | string | \"otoroshi\" | the subdomain on wich Otoroshi backoffice will be served |\n| `app.backoffice.session.exp` | number | 86400000 | the number of seconds before the Otoroshi backoffice session expires |\n| `app.privateapps.subdomain` | string | \"privateapps\" | the subdomain on which private apps UI are served |\n| `app.privateapps.session.exp` | number | 86400000 | the number of seconds before the private apps session expires |\n| `app.claim.sharedKey` | string | \"secret\" | the shared secret used for signing the JWT token passed between Otoroshi and backend services |\n| `app.webhooks.size` | number | 100 | number of events sent at most when calling one of the analytics webhooks |\n| `app.throttlingWindow` | number | 10 | time window (in seconds) used to compute throttling quotas for ApiKeys |\n\n## Admin API configuration\n\nWhen Otoroshi starts for the first time, its datastore is empty. As Otoroshi uses Otoroshi to expose its admin REST API, you'll have to provide the details for the admin API exposition. **This part is super important** because if you go to production with the default values, your Otoroshi server won't be secured anymore.\n\n@@@ warning\nYOU HAVE TO CUSTOMIZE THE FOLLOWING VALUES BEFORE GOING TO PRODUCTION !!\n@@@\n\nSome of the following terms will seem obscure to you, but you will learn their meaning in the following chapters :)\n\n| name | type | default value | description |\n| ---- |:----:| -------------- | ----- |\n| `app.adminapi.exposed` | boolean | true | does the current Otoroshi instance expose an admin API |\n| `app.adminapi.targetSubdomain` | string | \"otoroshi-admin-internal-api\" | the subdomain on wich admin API call will be redirected from `app.adminapi.exposedSubdomain` |\n| `app.adminapi.exposedSubdomain` | string | \"otoroshi-api\" | the subdomain on wich the Otoroshi admin API will be exposed |\n| `app.adminapi.defaultValues.backOfficeGroupId` | string | \"admin-api-group\" | the name of the service groups that will contain the service descriptors for the Otoroshi admin API |\n| `app.adminapi.defaultValues.backOfficeApiKeyClientId` | string | \"admin-api-apikey-id\" | the client id of the Otoroshi admin API apikey |\n| `app.adminapi.defaultValues.backOfficeApiKeyClientSecret` | string | \"admin-api-apikey-secret\" | the client secret of the Otoroshi admin API apikey |\n| `app.adminapi.defaultValues.backOfficeServiceId` | string | \"admin-api-service\" | the id of the service descriptors for the Otoroshi admin API |\n| `app.adminapi.proxy.https` | boolean | false | whether or not the current Otoroshi instance serves its content over https. This setting is useful for the backoffice UI to access Otoroshi admin API |\n| `app.adminapi.proxy.local` | boolean | true | whether or not the admin API is accessible through `127.0.0.1`. This setting is useful for the backoffice UI to access Otoroshi admin API |\n\n## DB configuration\n\nAs Otoroshi supports multiple datastores, you'll have to provide some details about how to connect/configure it.\n\n| name | type | default value | description |\n| ---- |:----:| -------------- | ----- |\n| `app.storage` | string | \"inmemory\" | what kind of storage engine you want to use. Possible values are `inmemory`, `leveldb`, `redis`, `cassandra`, `mongo` |\n| `app.importFrom` | string | | a file path or a URL to an Otoroshi export file. If the datastore is empty on startup, this file will be used to import data to the empty DB |\n| `app.importFromHeaders` | array | [] | a list of `:` separated header to use if the `app.importFrom` setting is a URL |\n| `app.initialData` | object |  | object representing Otoroshi internal data as exported from danger zone so you don't need a config file and a data import file |\n| `app.redis.host` | string | \"localhost\" | the host of the redis server |\n| `app.redis.port` | number | 6379 | the port of the redis server |\n| `app.redis.slaves` | array | [] | the redis slaves lists |\n| `app.leveldb.path` | string | \"./leveldb\" | the path where levelDB files will be written |\n| `app.cassandra.hosts` | string | \"127.0.0.1\" | the host of the cassandra server |\n| `app.cassandra.host` | string | \"127.0.0.1\" | the list of cassandra hosts |\n| `app.cassandra.port` | number | 9042 | the port of the cassandra servers |\n| `app.mongo.uri` | string | \"mongodb://localhost:27017/default\" | the mongo URI following Mongo semantic https://docs.mongodb.com/manual/reference/connection-string/ |\n\n## Headers configuration\n\nOtoroshi uses a fair amount of http headers in order to work properly. The name of those headers are customizable to fit your needs.\n\n| name | type | default value | description |\n| ---- |:----:| -------------- | ----- |\n| `otoroshi.headers.trace.label` | string | \"Otoroshi-Viz-From-Label\" | header to pass request tracing informations |\n| `otoroshi.headers.trace.from` | string | \"Otoroshi-Viz-From\" | header to pass request tracing informations (ip address) |\n| `otoroshi.headers.trace.parent` | string | \"Otoroshi-Parent-Request\" | header to pass request tracing informations (parent request id) |\n| `otoroshi.headers.request.adminprofile` | string | \"Otoroshi-Admin-Profile\" | header to pass admin name when the admin API is called from the Otoroshi backoffice |\n| `otoroshi.headers.request.clientid` | string | \"Otoroshi-Client-Id\" | header to pass apikey client id |\n| `otoroshi.headers.request.clientsecret` | string | \"Otoroshi-Client-Secret\" | header to pass apikey client secret |\n| `otoroshi.headers.request.id` | string | \"Otoroshi-Request-Id\" | header containing the id of the current request |\n| `otoroshi.headers.response.proxyhost` | string | \"Otoroshi-Proxied-Host\" | header containing the proxied host |\n| `otoroshi.headers.response.error` | string | \"Otoroshi-Error\" | header containing whether or not the request generated an error |\n| `otoroshi.headers.response.errormsg` | string | \"Otoroshi-Error-Msg\" | header containing error message if some |\n| `otoroshi.headers.response.proxylatency` | string | \"Otoroshi-Proxy-Latency\" | header containing the current latency induced by Otoroshi |\n| `otoroshi.headers.response.upstreamlatency` | string | \"Otoroshi-Upstream-Latency\" | header containing the current latency from Otoroshi to service backend |\n| `otoroshi.headers.response.dailyquota` | string | \"Otoroshi-Daily-Calls-Remaining\" | header containing the number of remaining daily call (apikey) |\n| `otoroshi.headers.response.monthlyquota` | string | \"Otoroshi-Monthly-Calls-Remaining\" | header containing the number of remaining monthly call (apikey) |\n| `otoroshi.headers.comm.state` | string | \"Otoroshi-State\" | header containing a random value for secured mode |\n| `otoroshi.headers.comm.stateresp` | string | \"Otoroshi-State-Resp\" | header containing a random value for secured mode |\n| `otoroshi.headers.comm.claim` | string | \"Otoroshi-Claim\" | header containing a JWT token for secured mode |\n| `otoroshi.headers.healthcheck.test` | string | \"Otoroshi-Health-Check-Logic-Test\" | header containing a logic test for healthcheck |\n| `otoroshi.headers.healthcheck.testresult` | string | \"Otoroshi-Health-Check-Logic-Test-Result\" | header containing the result of a logic test for healthcheck |\n| `otoroshi.headers.jwt.issuer` | string | \"Otoroshi\" | the name of the issuer for the JWT token |\n| `otoroshi.headers.canary.tracker` | string | \"Otoroshi-Canary-Id\" | header containing the ID of the canary session if enabled |\n\n## Play specific configuration\n\nAs Otoroshi is a [Play app](https://www.playframework.com/), you should take a look at [Play configuration documentation](https://www.playframework.com/documentation/2.6.x/Configuration) to tune its internal configuration\n\n| name | type | default value | description |\n| ---- |:----:| -------------- | ----- |\n| `http.port` | number | 8080 | the http port used by Otoroshi. You can use 'disabled' as value if you don't want to use http |\n| `https.port` | number | disabled | the https port used by Otoroshi. You can use 'disabled' as value if you don't want to use https |\n| `http2.enabled` | boolean | false | whether or not http2 is enabled on the Otoroshi server. You need to configure https (listed bellow) to be able to use it |\n| `play.http.secret.key` | string | \"secret\" | the secret used to sign Otoroshi session cookie |\n| `play.http.session.secure` | boolean | false | whether or not the Otoroshi backoffice session will be served over https only |\n| `play.http.session.httpOnly` | boolean | true | whether or not the Otoroshi backoffice session will be accessible from Javascript |\n| `play.http.session.maxAge` | number | 259200000 | the number of seconds before Otoroshi backoffice session expired |\n| `play.http.session.domain` | string | \".oto.tools\" | the domain on which the Otoroshi backoffice session is authorized |\n| `play.http.session.cookieName` | string | \"otoroshi-session\" | the name of the Otoroshi backoffice session |\n| `play.ws.play.ws.useragent` | string | \"Otoroshi\" | the user agent sent by Otoroshi if not present on the original http request |\n| `play.server.https.keyStore.path` | string | | the path to the keystore containing the private key and certificate, if not provided generates a keystore for you |\n| `play.server.https.keyStore.type` | string | JKS | the key store type, defaults to JKS |\n| `play.server.https.keyStore.password` | string | '' | the password, defaults to a blank password |\n| `play.server.https.keyStore.algorithm` | string | | the key store algorithm, defaults to the platforms default algorithm |\n\n## More config. options\n\nSee https://github.com/MAIF/otoroshi/blob/master/otoroshi/conf/base.conf and https://github.com/MAIF/otoroshi/blob/master/otoroshi/conf/application.conf\n\nif you want to configure https on your Otoroshi server, just read [PlayFramework documentation about it](https://www.playframework.com/documentation/2.6.x/ConfiguringHttps)\n\n## Example of configuration file\n\n```conf\ninclude \"application.conf\"\n\nhttp.port = 8080\n\napp {\n storage = \"leveldb\"\n importFrom = \"./my-state.json\"\n env = \"prod\"\n domain = \"oto.tools\"\n rootScheme = \"http\"\n snowflake {\n seed = 0\n }\n events {\n maxSize = 1000\n }\n backoffice {\n subdomain = \"otoroshi\"\n session {\n exp = 86400000\n }\n }\n privateapps {\n subdomain = \"privateapps\"\n session {\n exp = 86400000\n }\n }\n adminapi {\n targetSubdomain = \"otoroshi-admin-internal-api\"\n exposedSubdomain = \"otoroshi-api\"\n defaultValues {\n backOfficeGroupId = \"admin-api-group\"\n backOfficeApiKeyClientId = \"admin-api-apikey-id\"\n backOfficeApiKeyClientSecret = \"admin-api-apikey-secret\"\n backOfficeServiceId = \"admin-api-service\"\n }\n }\n claim {\n sharedKey = \"mysecret\"\n }\n leveldb {\n path = \"./leveldb\"\n }\n}\n\nplay.http {\n session {\n secure = false\n httpOnly = true\n maxAge = 2592000000\n domain = \".oto.tools\"\n cookieName = \"oto-sess\"\n }\n}\n```\n"},{"name":"datastore.md","id":"/firstrun/datastore.md","url":"/firstrun/datastore.html","title":"Choose your datastore","content":"# Choose your datastore\n\nRight now, Otoroshi supports multiple datastore.\n\nYou can choose one datastore over another depending on your use case.\n\nAvailable datastores are the following :\n\n* in memory\n* redis\n* cassandra (experimental support)\n* mongodb (experimental support)\n* levelDB (not suitable for production usage)\n\nThe **levelDB** datastore is pretty handy for testing purposes, but is not supposed to be used in production mode.\n\nThe **in-memory** datastore is kind of interesting... It can be used for testing purposes, but it is also a good candidate for production because of its fastness. But in that case, you need to provide a way to feed the deployed in-memory instances after the initial boot. In a future release (i.e. not yet :D), we will provide a master/workers slave based on Otoroshi in memory instances and kafka (see https://github.com/MAIF/otoroshi/issues/8 for more details about the feature).\n\nThe **redis** datastore is quite nice when you want to easily deploy several Otoroshi instances.\n\nIf you need a datastore more scalable than redis, then you can use the **cassandra** datastore or the **mongodb** datastore.\n\nWe plan to add more datastores support in the future :)\n\n@@@ div { .centered-img }\n\n@@@\n"},{"name":"env.md","id":"/firstrun/env.md","url":"/firstrun/env.html","title":"Config. with ENVs","content":"# Config. with ENVs\n\nNow that you know @ref:[how to configure Otoroshi with the config. file](./configfile.md) every property in the following block can be overriden by an environment variable (an env. variable is written like `${?ENV_VARIABLE}`).\n\n```\napp.storage = ${?APP_STORAGE}\napp.importFrom = ${?APP_IMPORT_FROM}\napp.domain = ${?APP_DOMAIN}\napp.rootScheme = ${?APP_ROOT_SCHEME}\napp.throttlingWindow = ${?THROTTLING_WINDOW}\napp.snowflake.seed = ${?INSTANCE_NUMBER}\napp.events.maxSize = ${?MAX_EVENTS_SIZE}\napp.backoffice.exposed = ${?APP_BACKOFFICE_EXPOSED}\napp.backoffice.subdomain = ${?APP_BACKOFFICE_SUBDOMAIN}\napp.backoffice.session.exp = ${?APP_BACKOFFICE_SESSION_EXP}\napp.privateapps.subdomain = ${?APP_PRIVATEAPPS_SUBDOMAIN}\napp.privateapps.session.exp = ${?APP_PRIVATEAPPS_SESSION_EXP}\napp.adminapi.exposed = ${?ADMIN_API_EXPOSED}\napp.adminapi.targetSubdomain = ${?ADMIN_API_TARGET_SUBDOMAIN}\napp.adminapi.exposedSubdomain = ${?ADMIN_API_EXPOSED_SUBDOMAIN}\napp.adminapi.defaultValues.backOfficeGroupId = ${?ADMIN_API_GROUP}\napp.adminapi.defaultValues.backOfficeApiKeyClientId = ${?ADMIN_API_CLIENT_ID}\napp.adminapi.defaultValues.backOfficeApiKeyClientSecret = ${?ADMIN_API_CLIENT_SECRET}\napp.adminapi.defaultValues.backOfficeServiceId = ${?ADMIN_API_SERVICE_ID}\napp.adminapi.proxy.https = ${?ADMIN_API_HTTPS}\napp.adminapi.proxy.local = ${?ADMIN_API_LOCAL}\napp.claim.sharedKey = ${?CLAIM_SHAREDKEY}\napp.webhooks.size = ${?WEBHOOK_SIZE}\napp.redis.host = ${?REDIS_HOST}\napp.redis.port = ${?REDIS_PORT}\napp.redis.password = ${?REDIS_PASSWORD}\napp.redis.windowSize = ${?REDIS_WINDOW_SIZE}\napp.redis.useScan = ${?REDIS_USE_SCAN}\napp.inmemory.windowSize = ${?INMEMORY_WINDOW_SIZE}\napp.leveldb.windowSize = ${?LEVELDB_WINDOW_SIZE}\napp.leveldb.path = ${?LEVELDB_PATH}\napp.cassandra.windowSize = ${?CASSANDRA_WINDOW_SIZE}\napp.cassandra.hosts = ${?CASSANDRA_HOSTS}\napp.cassandra.host = ${?CASSANDRA_HOST}\napp.cassandra.port = ${?CASSANDRA_PORT}\napp.elastic.url = ${?ELASTIC_URL}\napp.elastic.user = ${?ELASTIC_USER}\napp.elastic.password = ${?ELASTIC_PASSWORD}\napp.elastic.index = ${?ELASTIC_INDEX}\napp.elastic.type = ${?ELASTIC_TYPE}\napp.mongo.uri = ${?MONGO_URI}\nhttp.port = ${?PORT}\nhttps.port = ${?HTTPS_PORT}\nhttp2.enabled = ${?HTTP2_ENABLED}\nplay.http.secret.key = ${?PLAY_CRYPTO_SECRET}\nplay.http.session.secure = ${?SESSION_SECURE_ONLY}\nplay.http.session.maxAge = ${?SESSION_MAX_AGE}\nplay.http.session.domain = ${?SESSION_DOMAIN}\nplay.http.session.cookieName = ${?SESSION_NAME}\nplay.ws.play.ws.useragent=${?USER_AGENT}\n```\n"},{"name":"host.md","id":"/firstrun/host.md","url":"/firstrun/host.html","title":"Setup your hosts","content":"# Setup your hosts\n\nBy default, Otoroshi starts with domain `oto.tools` that targets `127.0.0.1`. Of course you can change the domain, you have to add the values in your `/etc/hosts` file according to the setting you put in Otoroshi configuration\n\n* `app.domain` => `oto.tools`\n* `app.backoffice.subdomain` => `otoroshi`\n* `app.privateapps.subdomain` => `privateapps`\n* `app.adminapi.exposedSubdomain` => `otoroshi-api`\n* `app.adminapi.targetSubdomain` => `otoroshi-admin-internal-api`\n\nfor instance if you want to change the default domain and use something like `otoroshi.mydomain.org`, then start otoroshi like \n\n```sh\njava -Dapp.domain=mydomain.org -jar otoroshi.jar\n```\n\n@@@ warning\nOtoroshi cannot be accessed using `http://127.0.0.1:8080` or `http://localhost:8080` because Otoroshi uses Otoroshi to serve it's own UI and API. When otoroshi starts with an empty database, it will create a service descriptor for that using `app.domain` and the settings listed on this page and in the * [Config. with files page](./configfile.md) that serve Otoroshi API and UI on `http://otoroshi-api.${app.domain}` and `http://otoroshi.${app.domain}`.\nOnce the descriptor is saved in database, if you want to change `app.domain`, you'll have to edit the descriptor in the database or restart Otoroshi with an empty database.\n@@@\n"},{"name":"index.md","id":"/firstrun/index.md","url":"/firstrun/index.html","title":"First run","content":"# First run\n\nNow that you have your own distro of Otoroshi, it's time to run it. \n\nBut before doing so, you'll have to make some choices about some essential stuff in order to have your own customized version of Otoroshi.\n\nLet's start with the datastore\n\n\n@@@ index\n\n* [choose a datastore](./datastore.md)\n* [use custom config file](./configfile.md)\n* [use ENV](./env.md)\n* [initial state](./initialstate.md)\n* [Hosts](./host.md)\n* [Run](./run.md)\n\n@@@"},{"name":"initialstate.md","id":"/firstrun/initialstate.md","url":"/firstrun/initialstate.html","title":"Import initial state","content":"# Import initial state\n\nNow you are almost ready to run Otoroshi for the first time, but maybe you want to import data from previous Otoroshi installation in your current datastore.\n\nTo do that, you need to add the `app.importFrom` setting to the Otoroshi configuration (of `$APP_IMPORT_FROM` env).\n\nIt can be a file path or a URL\n\n## Example of export\n\n```json\n{\n \"config\": {\n \"lines\": [\"prod\"], \n \"limitConcurrentRequests\": true,\n \"maxConcurrentRequests\": 500,\n \"useCircuitBreakers\": true,\n \"apiReadOnly\": false,\n \"registerFromCleverHook\": false,\n \"u2fLoginOnly\": true,\n \"ipFiltering\": {\n \"whitelist\": [],\n \"blacklist\": []\n },\n \"throttlingQuota\": 100000,\n \"perIpThrottlingQuota\": 500,\n \"analyticsEventsUrl\": null,\n \"analyticsWebhooks\": [],\n \"alertsWebhooks\": [],\n \"alertsEmails\": [],\n \"endlessIpAddresses\": []\n },\n \"admins\": [],\n \"simpleAdmins\": [\n {\n \"username\": \"admin@otoroshi.io\",\n \"password\": \"xxxxxxxxxxxxxxxxx\",\n \"label\": \"Otoroshi Admin\",\n \"createdAt\": 1493971715708\n }\n ],\n \"serviceGroups\": [\n {\n \"id\": \"default\",\n \"name\": \"default-group\",\n \"description\": \"The default group\"\n },\n {\n \"id\": \"admin-api-group\",\n \"name\": \"Otoroshi Admin Api group\",\n \"description\": \"No description\"\n }\n ],\n \"apiKeys\": [\n {\n \"clientId\": \"admin-api-apikey-id\",\n \"clientSecret\": \"admin-api-apikey-secret\",\n \"clientName\": \"Otoroshi Backoffice ApiKey\",\n \"authorizedGroup\": \"admin-api-group\",\n \"enabled\": true,\n \"throttlingQuota\": 10000000,\n \"dailyQuota\": 10000000,\n \"monthlyQuota\": 10000000,\n \"metadata\": {}\n }\n ],\n \"serviceDescriptors\": [\n {\n \"id\": \"admin-api-service\",\n \"groupId\": \"admin-api-group\",\n \"name\": \"otoroshi-admin-api\",\n \"env\": \"prod\",\n \"domain\": \"oto.tools\",\n \"subdomain\": \"otoroshi-api\",\n \"targets\": [\n {\n \"host\": \"localhost:8080\",\n \"scheme\": \"http\"\n }\n ],\n \"root\": \"/\",\n \"enabled\": true,\n \"privateApp\": false,\n \"forceHttps\": false,\n \"maintenanceMode\": false,\n \"buildMode\": false,\n \"enforceSecureCommunication\": true,\n \"publicPatterns\": [],\n \"privatePatterns\": [],\n \"additionalHeaders\": {\n \"Host\": \"otoroshi-admin-internal-api.oto.tools\"\n },\n \"matchingHeaders\": {},\n \"ipFiltering\": {\n \"whitelist\": [],\n \"blacklist\": []\n },\n \"api\": {\n \"exposeApi\": false\n },\n \"healthCheck\": {\n \"enabled\": false,\n \"url\": \"/\"\n },\n \"metadata\": {}\n }\n ],\n \"errorTemplates\": []\n}\n```\n"},{"name":"run.md","id":"/firstrun/run.md","url":"/firstrun/run.html","title":"Run Otoroshi","content":"# Run Otoroshi\n\nNow you are ready to run Otoroshi. You can run the following command with some tweaks depending on the way you want to configure Otoroshi. If you want to pass a custom configuration file, use the `-Dconfig.file=/path/to/file.conf` flag in the following commands.\n\n## From .zip file\n\n```sh\nunzip otoroshi-dist.zip\ncd otoroshi-vx.x.x\n./bin/otoroshi\n```\n\n## From .jar file\n\nFor Java 8 & Java 11\n\n```sh\njava -jar otoroshi.jar\n```\n\n## From docker\n\n```sh\ndocker run -p \"8080:8080\" maif/otoroshi:1.4.8-dev\n```\n\nYou can also pass useful args like :\n\n```\ndocker run -p \"8080:8080\" otoroshi -Dconfig.file=/usr/app/otoroshi/conf/otoroshi.conf -Dlogger.file=/usr/app/otoroshi/conf/otoroshi.xml\n```\n\nIf you want to provide your own config file, you can read @ref:[the documentation about config files](../firstrun/configfile.md).\n\nYou can also provide some ENV variable using the `--env` flag to customize your Otoroshi instance.\n\nThe list of possible env variables is available @ref:[here](../firstrun/env.md).\n\nYou can use a volume to provide configuration like :\n\n```sh\ndocker run -p \"8080:8080\" -v \"$(pwd):/usr/app/otoroshi/conf\" maif/otoroshi\n```\n\nYou can also use a volume if you choose to use `leveldb` datastore like :\n\n```sh\ndocker run -p \"8080:8080\" -v \"$(pwd)/leveldb:/usr/app/otoroshi/leveldb\" maif/otoroshi -Dapp.storage=leveldb\n```\n\nYou can also use a volume if you choose to use exports files :\n\n```sh\ndocker run -p \"8080:8080\" -v \"$(pwd):/usr/app/otoroshi/imports\" maif/otoroshi -Dapp.importFrom=/usr/app/otoroshi/imports/export.json\n```\n\n## Run examples\n\n```sh\n$ java \\\n -Xms2G \\\n -Xmx8G \\\n -Dhttp.port=8080 \\\n -Dapp.importFrom=/home/user/otoroshi.json \\\n -Dconfig.file=/home/user/otoroshi.conf \\\n -jar ./otoroshi.jar\n\n[warn] otoroshi-in-memory-datastores - Now using InMemory DataStores\n[warn] otoroshi-env - The main datastore seems to be empty, registering some basic services\n[warn] otoroshi-env - Importing from: /home/user/otoroshi.json\n[info] play.api.Play - Application started (Prod)\n[info] p.c.s.AkkaHttpServer - Listening for HTTP on /0:0:0:0:0:0:0:0:8080\n```\n\nIf you choose to start Otoroshi without importing existing data, Otoroshi will create a new admin user and print the login details in the log. When you will log into the admin dashboard, Otoroshi will ask you to create another account to avoid security issues.\n\n```sh\n$ java \\\n -Xms2G \\\n -Xmx8G \\\n -Dhttp.port=8080 \\\n -jar otoroshi.jar\n\n[warn] otoroshi-in-memory-datastores - Now using InMemory DataStores\n[warn] otoroshi-env - The main datastore seems to be empty, registering some basic services\n[warn] otoroshi-env - You can log into the Otoroshi admin console with the following credentials: admin@otoroshi.io / HHUsiF2UC3OPdmg0lGngEv3RrbIwWV5W\n[info] play.api.Play - Application started (Prod)\n[info] p.c.s.AkkaHttpServer - Listening for HTTP on /0:0:0:0:0:0:0:0:8080\n```\n"},{"name":"frombinaries.md","id":"/getotoroshi/frombinaries.md","url":"/getotoroshi/frombinaries.html","title":"From binaries","content":"# From binaries\n\nIf you want to download the last version of Otoroshi and its CLI, you can grab them from the release page of the Otoroshi github page :\n\nGo to https://github.com/MAIF/otoroshi/releases and get the last version of the `otoroshi-dist.zip` file or `otoroshi.jar` file\n"},{"name":"fromdocker.md","id":"/getotoroshi/fromdocker.md","url":"/getotoroshi/fromdocker.html","title":"From docker","content":"# From docker\n\nIf you're a Docker aficionado, Otoroshi is provided as a Docker image that your can pull directly from Official repos.\n\nfirst, fetch the last Docker image of Otoroshi :\n\n```sh\ndocker pull maif/otoroshi:1.4.18\n# or \ndocker pull maif/otoroshi:latest\n# or \ndocker pull maif/otoroshi:jdk8-1.4.18\n# or \ndocker pull maif/otoroshi:jdk11-1.4.18\n# or \ndocker pull maif/otoroshi:jdk12-1.4.18\n# or \ndocker pull maif/otoroshi:jdk13-1.4.18\n# or \ndocker pull maif/otoroshi:jdk14-1.4.18\n```"},{"name":"fromsources.md","id":"/getotoroshi/fromsources.md","url":"/getotoroshi/fromsources.html","title":"From sources","content":"# From sources\n\nto build Otoroshi from sources, you need the following tools :\n\n* git\n* JDK 8\n* SBT\n* node\n* yarn\n\nOnce you've installed all those tools, go to the [Otoroshi github page](https://github.com/MAIF/otoroshi) and clone the sources :\n\n```sh\ngit clone https://github.com/MAIF/otoroshi.git --depth=1\n```\n\nthen you need to run the `build.sh` script to build the documentation, the React UI and the server :\n\n```sh\nsh ./scripts/build.sh\n```\n\nand that's all, you can grab your Otoroshi package at `otoroshi/target/scala-2.12/otoroshi` or `otoroshi/target/universal/`.\n\nFor those who want to build only parts of Otoroshi, read the following.\n\n## Build the documentation only\n\nGo to the `documentation` folder and run :\n\n```sh\nsbt ';clean;paradox'\n```\n\nThe documentation is located at `documentation/target/paradox/site/main/`\n\n## Build the React UI\n\nGo to the `otoroshi/javascript` folder and run :\n\n```sh\nyarn install\nyarn build\n```\n\nYou will find the JS bundle at `otoroshi/public/javascripts/bundle/bundle.js`.\n\n## Build the Otoroshi server\n\nGo to the `otoroshi` folder and run :\n\n```sh\nsbt ';clean;compile;dist;assembly'\n```\n\nYou will find your Otoroshi package at `otoroshi/target/scala-2.12/otoroshi` or `otoroshi/target/universal/`.\n"},{"name":"index.md","id":"/getotoroshi/index.md","url":"/getotoroshi/index.html","title":"Get Otoroshi","content":"# Get Otoroshi\n\nThere are several ways to get Otoroshi to run it on your system.\n\nLet's start with a good old build from sources :)\n\n@@@ index\n\n* [from sources](./fromsources.md)\n* [from binaries](./frombinaries.md)\n* [from docker](./fromdocker.md)\n\n@@@"},{"name":"index.md","id":"/index.md","url":"/index.html","title":"Otoroshi","content":"# Otoroshi\n\n**Otoroshi** is a layer of lightweight api management on top of a modern http reverse proxy written in Scala and developped by the MAIF OSS team that can handle all the calls to and between your microservices without service locator and let you change configuration dynamicaly at runtime.\n\n\n> *The Otoroshi is a large hairy monster that tends to lurk on the top of the torii gate in front of Shinto shrines. It's a hostile creature, but also said to be the guardian of the shrine and is said to leap down from the top of the gate to devour those who approach the shrine for only self-serving purposes.*\n\n@@@ div { .centered-img }\n[![Build Status](https://travis-ci.org/MAIF/otoroshi.svg?branch=master)](https://travis-ci.org/MAIF/otoroshi) [![Join the chat at https://gitter.im/MAIF/otoroshi](https://badges.gitter.im/MAIF/otoroshi.svg)](https://gitter.im/MAIF/otoroshi?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [ ![Download](https://img.shields.io/github/release/MAIF/otoroshi.svg) ](hhttps://github.com/MAIF/otoroshi/releases/download/v1.4.18/otoroshi.jar)\n@@@\n\n@@@ div { .centered-img }\n\n@@@\n\n## Installation\n\nYou can download the latest build of Otoroshi as a [fat jar](https://github.com/MAIF/otoroshi/releases/download/v1.4.18/otoroshi.jar), as a [zip package](https://github.com/MAIF/otoroshi/releases/download/v1.4.18/otoroshi-dist.zip) or as a @ref:[docker image](./getotoroshi/fromdocker.md).\n\nYou can install and run Otoroshi with this little bash snippet\n\n```sh\ncurl -L -o otoroshi.jar 'https://github.com/MAIF/otoroshi/releases/download/v1.4.18/otoroshi.jar'\njava -jar otoroshi.jar\n```\n\nor using docker\n\n```sh\ndocker run -p \"8080:8080\" maif/otoroshi:1.4.18\n```\n\nnow open your browser to http://otoroshi.oto.tools:8080/, **log in with the credential generated in the logs** and explore by yourself, if you want better instructions, just go to the @ref:[Quick Start](./quickstart.md) or directly to the @ref:[installation instructions](./getotoroshi/index.md)\n\n## Documentation\n\n* @ref:[About Otoroshi](./about.md)\n* @ref:[Architecture](./archi.md)\n* @ref:[Features](./features.md)\n* @ref:[Try Otoroshi in 5 minutes](./quickstart.md)\n* @ref:[Video tutorials](./videos.md)\n* @ref:[Get Otoroshi](./getotoroshi/index.md)\n* @ref:[First run](./firstrun/index.md)\n* @ref:[Setup Otoroshi](./setup/index.md)\n* @ref:[Using Otoroshi](./usage/index.md)\n* @ref:[Third party Integrations](./integrations/index.md)\n* @ref:[Detailed topics](./topics/index.md)\n* @ref:[Embedding Otoroshi](./embedding.md)\n* @ref:[Admin REST API](./api.md)\n* @ref:[Rust CLI](./cli.md)\n* @ref:[Deploy to production](./deploy/index.md)\n* @ref:[Connectors](./connectors/index.md)\n\n## Discussion\n\nJoin the [Otoroshi](https://gitter.im/MAIF/otoroshi) channel on the [MAIF Gitter](https://gitter.im/MAIF)\n\n## Sources\n\nThe sources of Otoroshi are available on [Github](https://github.com/MAIF/otoroshi).\n\n## Logo\n\nYou can find the official Otoroshi logo [on GitHub](https://github.com/MAIF/otoroshi/blob/master/resources/otoroshi-logo.png). The Otoroshi logo has been created by François Galioto ([@fgalioto](https://twitter.com/fgalioto))\n\n## Changelog\n\nEvery release, along with the migration instructions, is documented on the [Github Releases](https://github.com/MAIF/otoroshi/releases) page.\n\n## Patrons\n\nThe work on Otoroshi was funded by MAIF with the help of the community.\n\n## Licence\n\nOtoroshi is Open Source and available under the [Apache 2 License](https://opensource.org/licenses/Apache-2.0)\n\n@@@ index\n\n* [About Otoroshi](about.md)\n* [Architecture](archi.md)\n* [Features](features.md)\n* [Quickstart](quickstart.md)\n* [Videos](videos.md)\n* [Get otoroshi](getotoroshi/index.md)\n* [First run](firstrun/index.md)\n* [Setup](setup/index.md)\n* [Using Otoroshi](usage/index.md)\n* [Integrations](integrations/index.md)\n* [Detailed topics](topics/index.md)\n* [Admin REST API](api.md)\n* [Embedding Otoroshi](./embedding.md)\n* [Official Rust CLI](cli.md)\n* [Deploy to production](deploy/index.md)\n* [Connectors](connectors/index.md)\n\n@@@\n"},{"name":"analytics.md","id":"/integrations/analytics.md","url":"/integrations/analytics.html","title":"Analytics","content":"# Analytics\n\nEach action and request on Otoroshi creates events that can be sent outside of Otoroshi for further usage. Those events can be sent using a webhook and/or through a Kafka topic.\n\n## Push events to Elasticsearch\n\nYou can use elastic search to store otoroshi events. To do this you have to configure the access to elasticsearch from `settings (cog icon) / Danger Zone` and expand the `Analytics: Elastic cluster (write)` section.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Read events from Elasticsearch\n\nYou can use elastic search to store otoroshi events. To do this you have to configure the access to elasticsearch from `settings (cog icon) / Danger Zone` and expand the `Analytics: Elastic dashboard datasource (read)` section.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Push events to WebHooks\n\nGo to `settings (cog icon) / Danger Zone` and expand the `Analytics: Webhooks` section.\n\n@@@ div { .centered-img }\n\n@@@\n\nHere you can configure the URL of the webhook and its headers if needed.\n\n## Push events to Kafka\n\nEvents can also be sent through a Kafka topic. Go to `settings (cog icon) / Danger Zone` and expand the `Analytics: Kafka` section.\n\n@@@ div { .centered-img }\n\n@@@\n\nFill the form, default values for topic names are :\n\n* `otoroshi-alerts`\n* `otoroshi-analytics`\n* `otoroshi-audits`\n\n@@@ warning\nIf you use trustore/keystore to access your kafka instances, the paths should be absolute and refers to host paths.\n@@@\n"},{"name":"auth0.md","id":"/integrations/auth0.md","url":"/integrations/auth0.html","title":"Auth0","content":"# Auth0\n\nYou can use Auth0 to log into Otoroshi's backoffice and Otoroshi's private apps.\n\nGo to `settings (cog icon) / Danger Zone` and expand the `Backoffice Auth0 settings` and `Private apps Auth0 settings` sections.\n\n@@@ div { .centered-img }\n\n@@@\n\nNow, you can fill the following fields :\n\n* `Client Id`\n* `Client Service`\n* `Domain`\n\nFor the `Callback URL` fields, use something like\n\n```\nhttps://otoroshi.oto.tools/backoffice/auth0/callback\nhttps://privateapps.oto.tools/privateapps/auth0/callback\n```\n\nOf course, you need to replace `otoroshi.oto.tools` and `privateapps.oto.tools` with your own domain and sub-domains. Don't forget to customize the callback URLs in your Auth0 backoffice too.\n\nNow if you logout, you will see the Auth0 option on the login screen\n\n@@@ div { .centered-img }\n\n@@@\n"},{"name":"clevercloud.md","id":"/integrations/clevercloud.md","url":"/integrations/clevercloud.html","title":"Clever Cloud","content":"# Clever Cloud\n\nOtoroshi provides an integration with Clever Cloud to create easily services based on application deployed on your Clever Cloud account.\nGo to `settings (cog icon) / Danger Zone` and expand the `CleverCloud settings` section.\n\n@@@ div { .centered-img }\n\n@@@\n\nFill the form with your CleverCloud credentials (https://www.clever-cloud.com/doc/clever-cloud-apis/cc-api/) and your CleverCloud `organization id`.\n\nOnce it's done, you will see a new menu in the side bar.\n\n@@@ div { .centered-img }\n\n@@@\n\nIf you click on it, you'll see a page listing all your apps deployed on Clever Cloud with buttons to create new services with the app as the target.\n\n@@@ div { .centered-img }\n\n@@@\n\nYou will also see a new button in the `Target` section of services to attach Clever Cloud applications as target for a service.\n\n@@@ div { .centered-img }\n\n@@@\n"},{"name":"index.md","id":"/integrations/index.md","url":"/integrations/index.html","title":"Third party Integrations","content":"# Third party Integrations\n\nOtoroshi provides some settings to interact with some third party systems.\n\n@@@ index\n\n* [Analytics](./analytics.md)\n* [Mailgun](./mailgun.md)\n* [StatsD / Datadog](./statsd.md)\n* [clevercloud](./clevercloud.md)\n\n@@@\n"},{"name":"mailgun.md","id":"/integrations/mailgun.md","url":"/integrations/mailgun.html","title":"Mailgun","content":"# Mailgun\n\nIf you want to receive Otoroshi alert by emails, you have to configure Otoroshi with your Mailgun credentials. Go to `settings (cog icon) / Danger Zone` and expand the `Mailgun settings` section.\n\n@@@ div { .centered-img }\n\n@@@\n\nFill the form with provided information on the `domain informations` page on Mailgun located at https://app.mailgun.com/app/domains/my.domain.\n\nThen, expand the `Alert settings` section and add email addresses separated by comma in the `Alert emails` field. **Don't forget to save.**\n\n@@@ div { .centered-img }\n\n@@@\n"},{"name":"statsd.md","id":"/integrations/statsd.md","url":"/integrations/statsd.html","title":"StatsD / Datadog","content":"# StatsD / Datadog\n\nOtoroshi provides a StatsD integration to monitor some technical metrics across all your Otoroshi instances.\nGo to `settings (cog icon) / Danger Zone` and expand the `Statsd settings` section.\n\n@@@ div { .centered-img }\n\n@@@\n\nAdd the host and port of the Statsd agent on your system.\nIf you're using Datadog, don't forget to check the `Datadog` switch.\n"},{"name":"quickstart.md","id":"/quickstart.md","url":"/quickstart.html","title":"Try Otoroshi in 5 minutes","content":"# Try Otoroshi in 5 minutes\n\nwhat you will need :\n\n* JDK 8\n* curl\n* 5 minutes of free time\n\nIf you don't/can't have these tools on your machine, you can start a sandboxed environment using here with the following command\n\n```sh\ndocker run -p \"8080:8080\" -it maif/otoroshi bash\n```\n\nor you can also try Otoroshi online with Google Cloud Shell if you're a user of the Google Cloud Platform\n\n@@@ div { .centered-img }\n[![Open in Cloud Shell](http://gstatic.com/cloudssh/images/open-btn.svg)](https://console.cloud.google.com/cloudshell/open?git_repo=https%3A%2F%2Fgithub.com%2Fmathieuancelin%2Fotoroshi-tutorial&page=shell&tutorial=tutorial.md)\n@@@\n\n## The elevator pitch\n\nOtoroshi is an awesome reverse proxy built with Scala that handles all the calls to and between your microservices without service locator and lets you change configuration dynamically at runtime.\n\n## I like sh but I really want to see the UI\n\nAs Otoroshi uses Otoroshi to serve its own admin UI and admin API, you won't be able to access the admin UI on `http://localhost:8080`. Otoroshi needs a domain name to know that you want to access the admin UI. By default, the admin UI is exposed on `http://otoroshi.oto.tools:8080`. Of course you can @ref:[configure](./firstrun/configfile.md#common-configuration) the domain of the subdomain. To configure access to the admin, just go to the [UI section of the quickstart](#what-about-the-ui).\n\n## Now some sh :)\n\n```sh\ncurl -L -o otoroshi.jar https://github.com/MAIF/otoroshi/releases/download/v1.4.18/otoroshi.jar\ncurl -L -o otoroshicli https://github.com/MAIF/otoroshi/releases/download/v1.4.18/linux-otoroshicli\n\nchmod +x otoroshicli\n\n# Run the Otoroshi server on Java 8\njava -jar otoroshi.jar &\n\n# Run the Otoroshi server on Java 9 and 10\njava --add-modules java.xml.bind -jar otoroshi.jar &\n\n# Check if admin api works\n./otoroshicli services all\n./otoroshicli apikeys all\n./otoroshicli groups all\n\n# Create a service that will proxy call to https://freegeoip.net through http://ip.geo.com:8080\n./otoroshicli services create --group default --name geo-ip-api --env prod \\\n --domain geo.com --subdomain ip --root /json/ --target https://freegeoip.net \\\n --public-pattern '/.*' --no-force-https\n\n# Then test it\n./otoroshicli tryout call \"http://127.0.0.1:8080/\" -X GET -H 'Host: ip.geo.com'\n# works with curl -X GET -H 'Host: ip.geo.com' \"http://127.0.0.1:8080/\" | jqn\n\n# Run 3 new microservices in 3 new terminal processes\n./otoroshicli tryout serve 9901\n./otoroshicli tryout serve 9902\n./otoroshicli tryout serve 9903\n\n# Create a service that will loadbalance between these 3 microservices and serves them through\n# http://api.hello.com:8080\n./otoroshicli services create --group default --id hello-api --name hello-api \\\n --env prod --domain hello.com --subdomain api --root / \\\n --target \"http://127.0.0.1:9901\" \\\n --target \"http://127.0.0.1:9902\" \\\n --public-pattern '/.*' --no-force-https --client-retries 3\n\n# Then test it multiple time to observe loadbalancing\n# You can also define '127.0.0.1 api.hello.com' in your /etc/hosts file and test it in your browser\n./otoroshicli tryout call \"http://127.0.0.1:8080/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\nsudo echo \"127.0.0.1 api.hello.com\" >> /etc/hosts\nopen \"http://api.hello.com:8080/\"\n\n# Then add a new target\n./otoroshicli services add-target hello-api --target=\"http://127.0.0.1:9903\"\n\n# Then test it multiple time to observe loadbalancing\n./otoroshicli tryout call \"http://127.0.0.1:8080/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\n\n# Then kill one of the microservices and test it multiple time to observe loadbalancing\n./otoroshicli tryout call \"http://127.0.0.1:8080/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\n\n# Then kill a second microservices and test it multiple time to observe loadbalancing\n./otoroshicli tryout call \"http://127.0.0.1:8080/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\n\n# Then kill the last microservices and test it to observe connection error\n./otoroshicli tryout call \"http://127.0.0.1:8080/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\n\n# Then delete your service\n./otoroshicli services delete hello-api\n```\n\n## What about the UI\n\nGo to http://otoroshi.oto.tools:8080/ and **log in with the credential generated in the logs** during first startup ;-)\n\nIf you want to know more about Otoroshi, you should continue reading the documentation starting with @ref:[how to get Otoroshi](./getotoroshi/index.md)\n\n## I don't have JDK 8 on my machine but I have Docker :)\n\nIf you want to use Docker, just follow these instructions\n\n```sh\n# here be careful, if you want to change the OTOROSHI_PORT, change the same value in the `otoroshicli.toml` config file\nexport OTOROSHI_PORT=8080\nexport LOCAL_IP_ADDRESS=999.999.999.999 # use your real local ip address here\n\ncurl -L -o otoroshicli https://github.com/MAIF/otoroshi/releases/download/v1.4.18/linux-otoroshicli\n\nchmod +x otoroshicli \n\ndocker run -p \"$OTOROSHI_PORT:8080\" maif/otoroshi &\n\n# Check if admin api works\n./otoroshicli services all\n./otoroshicli apikeys all\n./otoroshicli groups all\n\n# Create a service that will proxy call to https://freegeoip.net through http://ip.geo.com:$OTOROSHI_PORT\n./otoroshicli services create --group default --name geo-ip-api --env prod \\\n --domain geo.com --subdomain ip --root /json/ --target https://freegeoip.net \\\n --public-pattern '/.*' --no-force-https\n\n# Then test it\n./otoroshicli tryout call \"http://127.0.0.1:$OTOROSHI_PORT/\" -X GET -H 'Host: ip.geo.com'\n# works with curl -X GET -H 'Host: ip.geo.com' \"http://127.0.0.1:$OTOROSHI_PORT/\" | jqn\n\n# Run 3 new microservices in 3 new terminal processes\n./otoroshicli tryout serve 9901\n./otoroshicli tryout serve 9902\n./otoroshicli tryout serve 9903\n\n# Create a service that will loadbalance between these 3 microservices and serves them through\n# http://api.hello.com:$OTOROSHI_PORT\n./otoroshicli services create --group default --id hello-api --name hello-api \\\n --env prod --domain hello.com --subdomain api --root / \\\n --target \"http://$LOCAL_IP_ADDRESS:9901\" \\\n --target \"http://$LOCAL_IP_ADDRESS:9902\" \\\n --public-pattern '/.*' --no-force-https --client-retries 3\n\n# Then test it multiple time to observe loadbalancing\n# You can also define '127.0.0.1 api.hello.com' in your /etc/hosts file and test it in your browser\n./otoroshicli tryout call \"http://127.0.0.1:$OTOROSHI_PORT/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\nsudo echo \"127.0.0.1 api.hello.com\" >> /etc/hosts\nopen \"http://api.hello.com:$OTOROSHI_PORT/\"\n\n# Then add a new target\n./otoroshicli services add-target hello-api --target=\"http://$LOCAL_IP_ADDRESS:9903\"\n\n# Then test it multiple time to observe loadbalancing\n./otoroshicli tryout call \"http://127.0.0.1:$OTOROSHI_PORT/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\n\n# Then kill one of the microservices and test it multiple time to observe loadbalancing\n./otoroshicli tryout call \"http://127.0.0.1:$OTOROSHI_PORT/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\n\n# Then kill a second microservices and test it multiple time to observe loadbalancing\n./otoroshicli tryout call \"http://127.0.0.1:$OTOROSHI_PORT/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\n\n# Then kill the last microservices and test it to observe connection error\n./otoroshicli tryout call \"http://127.0.0.1:$OTOROSHI_PORT/\" -H 'Host: api.hello.com' -H 'Accept: application/json'\n\n# Then delete your service\n./otoroshicli services delete hello-api\n```\n"},{"name":"admin.md","id":"/setup/admin.md","url":"/setup/admin.html","title":"Manage admin users","content":"# Manage admin users\n\n## Create admin user after the first run\n\nClick on the `Create an admin user` warning popup, or go to `settings (cog icon) / Admins`.\n\n@@@ div { .centered-img }\n\n@@@\n\nYou will see the list of registered admin users.\n\n@@@ div { .centered-img }\n\n@@@\n\nNow, enter informations about the new admin you want to create.\n\n@@@ div { .centered-img }\n\n@@@\n\nClick on `Register Admin`.\n\n@@@ div { .centered-img }\n\n@@@\n\nNow, you can discard the generated admin, confirm, then logout, login with the admin user you have just created and the danger popup will go away\n\n@@@ div { .centered-img }\n\n@@@\n\n## Create admin user with U2F device login\n\nGo to `settings (cog icon) / Admins`.\n\n@@@ div { .centered-img }\n\n@@@\n\nEnter informations about the new admin you want to create.\n\n@@@ div { .centered-img }\n\n@@@\n\nClick on `Register FIDO U2F Admin`.\n\nOtoroshi will ask you to plug your FIDO U2F device and touch it to complete registration.\n\n@@@ div { .centered-img }\n\n@@@\n\n@@@ warning\nTo be able to use FIDO U2F devices, Otoroshi must be served over https\n@@@\n\n## Discard admin user\n\nGo to `settings (cog icon) / Admins`, at the bottom of the page, you will see a list of admin users that you can discard. Just click on the `Discard User` button on the right side of the row and confirm that you actually want to discard an admin user.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Admin sessions management\n\nGo to `settings (cog icon) / Admins sessions`, you will see a list of active admin user sessions\n\n@@@ div { .centered-img }\n\n@@@\n\nYou can either discard single sessions one by one using the `Discard Session` on each targeted row of the list or discard all active sessions using the `Discard all sessions` button at the top of the page.\n"},{"name":"dangerzone.md","id":"/setup/dangerzone.md","url":"/setup/dangerzone.html","title":"Configure the Danger zone","content":"# Configure the Danger zone\n\nNow that you have an actual admin account, go to `setting (cog icon) / Danger Zone` in order to configure your Otoroshi instance.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Commons settings\n\nThis part allows you to configure various things :\n\n* `No Auth0 login` => allow you to disabled Auth0 login to the Otoroshi admin dashboard\n* `API read only` => disable `writes` on the Otoroshi admin api\n* `Use HTTP streaming` => use http streaming for each response. It should always be true\n* `Use circuit breakers` => allow usage of circuit breakers for each service\n* `Digitus medius` => change the character of endless HTTP responses from `0` to `🖕`\n* `Limit concurrent requests` => allow you to specify a max number of concurrent requests on an Otoroshi instance to avoid overloading\n* `Max concurrent requests` => max allowed number of concurrent requests on an Otoroshi instance to avoid overloading\n* `Max HTTP/1.0 response size` => max size of an HTTP/1.0 responses, because they are memory mapped\n* `Max local events` => number of events stored localy (alerts and audits)\n* `lines` => at least one (`prod`). for other, it will allow you to declare urls like `service.line.domain.tld`. For prod it will be `service.domain.tld`\n\n@@@ div { .centered-img }\n\n@@@\n\n## Whitelist / blacklist settings\n\nOtoroshi is capable of filtering request by ip address, allowing or blocking requests.\n\nOtoroshi also provides a fun feature called `Endless HTTP responses`. If you put an ip address in that field, then, for any http request on Otoroshi, every response will be 128 GB of `0`.\n\n@@@ div { .centered-img }\n\n@@@\n\n@@@ note\nNote that you may provide ip address with wildcard like the following `42.42.*.42` or `42.42.42.*` or `42.42.*.*`\n@@@\n\n## Global throttling settings\n\nOtoroshi is capable of managing throttling at a global level. Here you can configure number of authorized requests per second on a single Otoroshi instance and the number of authorized request per second for a unique ip address.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Analytics settings\n\nOne on the major features of Otoroshi is being able of generating internal events. Those events are not stored in Otoroshi's datastore but can be sent using `WebHooks`. You can configure those `WebHooks` from the `Danger Zone`.\n\nOtoroshi is also capable of reading some analytics and displays it from another MAIF product called `Omoïkane`. As Omoikane is not publicly available yet, is capable of storing events in an [Elastic](https://www.elastic.co/) cluster. For more information about analytics and what it does, just go to the @ref:[detailed chapter](../integrations/analytics.md)\n\n## Kafka settings\n\nOne on the major features of Otoroshi is being able of generating internal events. These events are not stored in Otoroshi's datastore but can be sent using a [Kafka message broker](https://kafka.apache.org/). You can configure Kafka access from the `Danger Zone`.\n\nBy default, Otoroshi's alert events will be sent on `otoroshi-alerts` topic, Otoroshi's audit events will be sent on `otoroshi-audits` topic and Otoroshi's traffic events will be sent on `otoroshi-analytics` topic.\n\n@@@ warning\nKeystore and truststore paths are optional local path on the server hosting Otoroshi\n@@@\n\n@@@ div { .centered-img }\n\n@@@\n\nFor more information about Kafka integration and what it does, just go to the @ref:[detailed chapter](../integrations/analytics.md)\n\n## Alerts settings\n\nEach time a dangerous action or something unusual is performed on Otoroshi, it will create an alert and store it. You can be notified for each of these alerts using `WebHooks` or emails. To do so, just add the `WebHook` URL and optional headers in the `Danger Zone` or any email address you want (you can add more than one email address).\n\n@@@ div { .centered-img }\n\n@@@\n\n## StatsD settings\n\nOtoroshi is capable of sending internal metrics to a StatsD agent. Just put the host and port of you StatsD agent in the `Danger Zone` to collect these metrics. If you using [Datadog](https://www.datadoghq.com), don't forget to check the dedicated button :)\n\n@@@ div { .centered-img }\n\n@@@\n\nFor more information about StatsD integration and what it does, just go to the @ref:[detailed chapter](../integrations/statsd.md)\n\n## Auth0 settings\n\nIt is possible to configure Otoroshi to allow admin users to log in through Auth0. Otoroshi also provides a feature called `Private apps.` that allows you to force login to an Auth0 domain before accessing an app. You can create an Auth0 client (https://manage.auth0.com/#/clients) for each of those features (admin users login and private apps login) and customize it with any rule you want (don't forget allowed callbacks, like `http://otoroshi.oto.tools:8080/backoffice/auth0/callback` and `http://privateapps.oto.tools:8080/backoffice/auth0/callback`).\n\nOnce you're done, go to the settings of each client (https://manage.auth0.com/#/clients/xxxxxxxxxxxxxxxx/settings) to get the information needed for the `Danger Zone`.\n\n@@@ div { .centered-img }\n\n@@@\n\n@@@ div { .centered-img }\n\n@@@\n\nFor more information about Auth0 integration and what it does, just go to the @ref:[detailed chapter](../integrations/auth0.md)\n\n## Mailgun settings\n\nIf you want to send emails for every alert generated by Otoroshi, you need to configure your Mailgun credentials in the `Danger Zone`. These parameters are provided in you Mailgun domain dashboard (i.e. https://app.mailgun.com/app/domains/my.domain.oto.tools) in the information section.\n\n@@@ div { .centered-img }\n\n@@@\n\nFor more information about Mailgun integration and what it does, just go to the @ref:[detailed chapter](../integrations/mailgun.md)\n\n## CleverCloud settings\n\nAs we built our products to run on Clever-Cloud, Otoroshi has a close integration with Clever-Cloud. In this section of `Danger Zone` you can configure how to access Clever-Cloud API.\n\nTo generate the needed value, please refers to [Clever-Cloud documentation](https://www.clever-cloud.com/doc/clever-cloud-apis/cc-api/)\n\n@@@ div { .centered-img }\n\n@@@\n\nFor more information about Clever-Cloud integration and what it does, just go to the @ref:[detailed chapter](../integrations/clevercloud.md)\n\n## Import / exports and panic mode\n\nFor more details about imports and exports, please go to the @ref:[dedicated chapter](../usage/8-importsexports.md)\n\nAbout panic mode, it's an unusual feature that allows you to discard all current admin. sessions, allows only admin users with U2F devices to log back, and pass the API in read-only mode. Only a person who has access to Otoroshi's datastore will be able to turn it back on.\n\n@@@ div { .centered-img }\n\n@@@\n"},{"name":"index.md","id":"/setup/index.md","url":"/setup/index.html","title":"Setup Otoroshi","content":"# Setup Otoroshi\n\nNow that Otoroshi is running, you are ready to log into the Otoroshi admin dashboard and setup your instance. Just go to :\n\nhttp://otoroshi.oto.tools:8080\n\nand you will see the following page\n \n@@@ div { .centered-img }\n\n@@@\n\nnow click on the login button and you will see the login page\n\n@@@ div { .centered-img }\n\n@@@\n\n@@@ warning\nUse the credentials generated in Otoroshi **logs** during **first run**.\n@@@\n\n@@@ div { .centered-img #first-login-example }\n\n@@@\n\n(of course, you can change this url dependending on the configuration you provided to Otoroshi).\n\nOnce logged in, the first screen you'll see should look like :\n\n@@@ div { .centered-img #first-login }\n\n@@@\n\nAs you can see, Otoroshi is not really happy about you being logged with a generated admin account.\n\nBut we will fix that in the next chapter\n\n@@@ index\n\n* [create admins](./admin.md)\n* [configure danger zone](./dangerzone.md)\n\n@@@\n"},{"name":"toc.md","id":"/toc.md","url":"/toc.html","title":"Table of contents","content":"# Table of contents\n\n@@toc\n\n"},{"name":"clustering.md","id":"/topics/clustering.md","url":"/topics/clustering.html","title":"Otoroshi clustering","content":"# Otoroshi clustering\n\nOtoroshi can work as a cluster by default as you can spin many Otoroshi servers using the same datastore or datastore cluster. In that case any instance is capable of serving services, Otoroshi admin UI, Otoroshi admin API, etc.\n\nBut sometimes, this is not enough. So Otoroshi provides an additional clustering model named `Leader / Workers` where there is a leader cluster ([control plane](https://en.wikipedia.org/wiki/Control_plane)), composed of Otoroshi instances backed by a datastore like Redis, Cassandra or Mongo, that is in charge of all `writes` to the datastore through Otoroshi admin UI and API, and a worker cluster ([data plane](https://en.wikipedia.org/wiki/Forwarding_plane)) composed of horizontally scalable Otoroshi instances, backed by a super fast in memory datastore, with the sole purpose of routing traffic to your services based on data synced from the leader cluster. With this distributed Otoroshi version, you can reach your goals of high availability, scalability and security.\n\nOtoroshi clustering only uses http internally (right now) to make communications between leaders and workers instances so it is fully compatible with PaaS providers like [Clever-Cloud](https://www.clever-cloud.com/en/) that only provide one external port for http traffic.\n\n@@@ div { .centered-img }\n\n\n*Fig. 1: Simplified view*\n@@@\n\n@@@ div { .centered-img }\n\n\n*Fig. 2: Deployment view*\n@@@\n\n## Cluster configuration\n\n```hocon\notoroshi {\n cluster {\n mode = \"leader\" # can be \"off\", \"leader\", \"worker\"\n compression = 4 # compression of the data sent between leader cluster and worker cluster. From -1 (disabled) to 9\n leader {\n name = ${?CLUSTER_LEADER_NAME} # name of the instance, if none, it will be generated\n urls = [\"http://127.0.0.1:8080\"] # urls to contact the leader cluster\n host = \"otoroshi-api.oto.tools\" # host of the otoroshi api in the leader cluster\n clientId = \"apikey-id\" # otoroshi api client id\n clientSecret = \"secret\" # otoroshi api client secret\n cacheStateFor = 4000 # state is cached during (ms)\n }\n worker {\n name = ${?CLUSTER_WORKER_NAME} # name of the instance, if none, it will be generated\n retries = 3 # number of retries when calling leader cluster\n timeout = 2000 # timeout when calling leader cluster\n state {\n retries = ${otoroshi.cluster.worker.retries} # number of retries when calling leader cluster on state sync\n pollEvery = 10000 # interval of time (ms) between 2 state sync\n timeout = ${otoroshi.cluster.worker.timeout} # timeout when calling leader cluster on state sync\n }\n quotas {\n retries = ${otoroshi.cluster.worker.retries} # number of retries when calling leader cluster on quotas sync\n pushEvery = 2000 # interval of time (ms) between 2 quotas sync\n timeout = ${otoroshi.cluster.worker.timeout} # timeout when calling leader cluster on quotas sync\n }\n }\n }\n}\n```\n\nyou can also use many env. variables to configure Otoroshi cluster\n\n```hocon\notoroshi {\n cluster {\n mode = ${?CLUSTER_MODE}\n compression = ${?CLUSTER_COMPRESSION}\n leader {\n name = ${?CLUSTER_LEADER_NAME}\n host = ${?CLUSTER_LEADER_HOST}\n url = ${?CLUSTER_LEADER_URL}\n clientId = ${?CLUSTER_LEADER_CLIENT_ID}\n clientSecret = ${?CLUSTER_LEADER_CLIENT_SECRET}\n groupingBy = ${?CLUSTER_LEADER_GROUP_BY}\n cacheStateFor = ${?CLUSTER_LEADER_CACHE_STATE_FOR}\n stateDumpPath = ${?CLUSTER_LEADER_DUMP_PATH}\n }\n worker {\n name = ${?CLUSTER_WORKER_NAME}\n retries = ${?CLUSTER_WORKER_RETRIES}\n timeout = ${?CLUSTER_WORKER_TIMEOUT}\n state {\n retries = ${?CLUSTER_WORKER_STATE_RETRIES}\n pollEvery = ${?CLUSTER_WORKER_POLL_EVERY}\n timeout = ${?CLUSTER_WORKER_POLL_TIMEOUT}\n }\n quotas {\n retries = ${?CLUSTER_WORKER_QUOTAS_RETRIES}\n pushEvery = ${?CLUSTER_WORKER_PUSH_EVERY}\n timeout = ${?CLUSTER_WORKER_PUSH_TIMEOUT}\n }\n }\n }\n}\n```\n\n@@@ warning\nYou **should** use HTTPS exposition for the Otoroshi API that will be used for data sync as sensitive informations are exchanged between control plane and data plane.\n@@@\n\n@@@ warning\nYou **must** have the same cluster configuration on every Otoroshi instance (worker/leader) with only names and mode changed for each instance. Some things in leader/worker are computed using configuration of their counterpart worker/leader.\n@@@\n\n## Cluster UI\n\nOnce an Otoroshi instance is launcher as cluster Leader, a new row of live metrics tile will be available on the home page of Otoroshi admin UI.\n\n@@@ div { .centered-img }\n\n@@@\n\nyou can also access a more detailed view of the cluster at `Settings (cog icon) / Cluster View`\n\n@@@ div { .centered-img }\n\n@@@\n\n## Run examples\n\nfor leader \n\n```sh\njava -Dhttp.port=8091 -Dhttps.port=9091 -Dotoroshi.cluster.mode=leader -jar otoroshi.jar\n```\n\nfor worker\n\n```sh\njava -Dhttp.port=8092 -Dhttps.port=9092 -Dotoroshi.cluster.mode=worker \\\n -Dotoroshi.cluster.leader.urls.0=http://127.0.0.1:8091 -jar otoroshi.jar\n```\n"},{"name":"index.md","id":"/topics/index.md","url":"/topics/index.html","title":"Detailed topics","content":"# Detailed topics\n\nIn this sections, you will find informations about various topics supported by Otoroshi\n\n@@@ index\n\n* [Chaos engineering with the Snow Monkey](./snow-monkey.md)\n* [Service mesh](./service-mesh.md)\n* [JWT Tokens verification](./jwt-verifications.md)\n* [SSL/TLS termination with Otoroshi](./ssl.md)\n* [Mutual TLS with Otoroshi](./mtls.md)\n* [Otoroshi Clustering](./clustering.md)\n* [Requests transformation](./req-transformers.md)\n* [Otoroshi monitoring](./monitoring.md)\n\n@@@\n"},{"name":"jwt-verifications.md","id":"/topics/jwt-verifications.md","url":"/topics/jwt-verifications.html","title":"JWT Tokens verification","content":"# JWT Tokens verification\n\nSometimes, it can be pretty useful to verify Jwt tokens coming from other provider on some services. Otoroshi provides a tool to do that per service. In the Service descriptor page, you can find a `Jwt token Verification` section dedicated to this topic.\n\n## Service descriptor local verifications\n\n@@@ div { .centered-img }\n\n@@@\n\nin this section you can select the type of verification you can choose if the verifier is local to the `Service descriptor` or reference a global one.\n\nYou can also enabled/disable jwt verification and activate strict mode. In strict mode, requests will be rejected if the jwt token is not found.\n\n### Jwt token location\n\nYou can use the `Source` selector to specify where the Jwt token can be found. \n\n* in a query string param\n\n@@@ div { .centered-img }\n\n@@@\n\n* in a header\n\n@@@ div { .centered-img }\n\n@@@\n\n* in a cookie\n\n@@@ div { .centered-img }\n\n@@@\n\n### Jwt signing\n\nYou can use the `Algo.` selector to specify the signing algorithm to use to verifiy the token\n\n@@@ div { .centered-img }\n\n@@@\n\nyou can choose between\n\n* Hmac + SHA256\n* Hmac + SHA384\n* Hmac + SHA512\n* RSA + SHA256\n* RSA + SHA384\n* RSA + SHA512\n* Elliptic Curve + SHA256\n* Elliptic Curve + SHA384\n* Elliptic Curve + SHA512\n\n@@@ div { .centered-img }\n\n@@@\n\nYou can use syntax like `${env.MY_ENV_VAR}` or `${config.my.config.path}` to provide secret/keys values. \n\n\n### Just verify signature and fields value\n\nUsing the `Verif. strategy` selector, you can choose `Verify jwt token`. This will verify if the token is signed using the settings from `jwt signing` section and the value of the fields provided in `Verify token fields`. Then the token will be send to the target just like that.\n\n@@@ div { .centered-img }\n\n@@@\n\n### Re-sign the token\n\nUsing the `Verif. strategy` selector, you can choose `Verify and re-sign jwt token`. This will verify if the token is signed using the settings from `jwt signing` section and the value of the fields provided in `Verify token fields`. Then the token will be re-signed using the settings provided in `Re-sign algo` and will be send to the target.\n\n@@@ div { .centered-img }\n\n@@@\n\n### Transform the token\n\nUsing the `Verif. strategy` selector, you can choose `Verify, re-sign and transform jwt token`. This will verify if the token is signed using the settings from `jwt signing` section and the value of the fields provided in `Verify token fields`. Then the token will be re-signed using the settings provided in `Re-sign algo`. You can also change the location of the token using `Token location`, remove fields using `Remove token fields`, set fields value using `Set token fields` and even rename fields using `Rename token fields`.\n\n@@@ div { .centered-img }\n\n@@@\n\nYou can also use a mini expression language in `Set token fields`. You just have to add expressions in values like `${expression}`. Supported expressions are the following :\n\n* `${date}` => set the current date\n* `${date.format('dd/MM/yyyy')}` => set the current date formatted with the format you want\n* `${token.fieldName}` => get the value of the field named `fieldName`\n* `${token.fieldName.replace('a', 'b')}` => get the value of the field named `fieldName` and replace `a` with `b`\n* `${token.fieldName.replaceAll('[0-9]', '-')}` => get the value of the field named `fieldName` and replace digits with `-`\n\nyou can of course use multiple expressions in one field like `my-value-is-${date}-with${token.user}`\n\n## Global verifications\n\nYou can create global jwt verifiers and reference them in your services (from the `Type` selector). When you set the type of verification to `Reference to a global definition`, you can choose an existing global jwt verifier\n\n@@@ div { .centered-img }\n\n@@@\n\nTo create a global verifier, go to `Settings (cog icon) / Global Jwt Verifiers` and it will display the list of global verifiers.\n\n@@@ div { .centered-img }\n\n@@@\n\nyou can them create, edit or delete verifiers\n\n@@@ div { .centered-img }\n\n@@@\n\n"},{"name":"monitoring.md","id":"/topics/monitoring.md","url":"/topics/monitoring.html","title":"Monitoring Otoroshi","content":"# Monitoring Otoroshi\n\nThe Otoroshi API exposes two endpoints for \n\n* `/health`: the health of the Otoroshi instance\n* `/metrics`: the metrics of the Otoroshi instance, either in JSON or Prometheus format using the `Accept` header (with `application/json` / `application/prometheus` values) or the `format` query param (with `json` or `prometheus` values)\n\n## Endpoints security\n\nThe two endpoints are exposed publicly on the Otoroshi admin api. But you can remove the corresponding public pattern and query the endpoints using standard apikeys. If you don't want to use apikeys but don't want to expose the endpoints publicly, you can defined two config. variables (`app.health.accessKey` or `HEALTH_ACCESS_KEY` and `otoroshi.metrics.accessKey` or `OTOROSHI_METRICS_ACCESS_KEY`) that will hold an access key for the endpoints. Then you can call the endpoints with an `access_key` query param with the value defined in the config. If you don't defined `otoroshi.metrics.accessKey` but define `app.health.accessKey`, `otoroshi.metrics.accessKey` will have the value of `app.health.accessKey`.\n \n## Examples\n\nlet say `app.health.accessKey` has value `MILpkVv6f2kG9Xmnc4mFIYRU4rTxHVGkxvB0hkQLZwEaZgE2hgbOXiRsN1DBnbtY`\n\n```sh\n$ curl http://otoroshi-api.oto.tools:8080/health\\?access_key\\=MILpkVv6f2kG9Xmnc4mFIYRU4rTxHVGkxvB0hkQLZwEaZgE2hgbOXiRsN1DBnbtY\n{\"otoroshi\":\"healthy\",\"datastore\":\"healthy\"}\n\n$ curl -H 'Accept: application/json' http://otoroshi-api.oto.tools:8080/metrics\\?access_key\\=MILpkVv6f2kG9Xmnc4mFIYRU4rTxHVGkxvB0hkQLZwEaZgE2hgbOXiRsN1DBnbtY\n{\"version\":\"4.0.0\",\"gauges\":{\"attr.app.commit\":{\"value\":\"xxxx\"},\"attr.app.id\":{\"value\":\"xxxx\"},\"attr.cluster.mode\":{\"value\":\"Leader\"},\"attr.cluster.name\":{\"value\":\"otoroshi-leader-0\"},\"attr.instance.env\":{\"value\":\"prod\"},\"attr.instance.id\":{\"value\":\"xxxx\"},\"attr.instance.number\":{\"value\":\"0\"},\"attr.jvm.cpu.usage\":{\"value\":136},\"attr.jvm.heap.size\":{\"value\":1409},\"attr.jvm.heap.used\":{\"value\":112},\"internals.0.concurrent-requests\":{\"value\":1},\"internals.global.throttling-quotas\":{\"value\":2},\"jvm.attr.name\":{\"value\":\"2085@xxxx\"},\"jvm.attr.uptime\":{\"value\":2296900},\"jvm.attr.vendor\":{\"value\":\"JDK11\"},\"jvm.gc.PS-MarkSweep.count\":{\"value\":3},\"jvm.gc.PS-MarkSweep.time\":{\"value\":261},\"jvm.gc.PS-Scavenge.count\":{\"value\":12},\"jvm.gc.PS-Scavenge.time\":{\"value\":161},\"jvm.memory.heap.committed\":{\"value\":1477967872},\"jvm.memory.heap.init\":{\"value\":1690304512},\"jvm.memory.heap.max\":{\"value\":3005218816},\"jvm.memory.heap.usage\":{\"value\":0.03916456777568639},\"jvm.memory.heap.used\":{\"value\":117698096},\"jvm.memory.non-heap.committed\":{\"value\":166445056},\"jvm.memory.non-heap.init\":{\"value\":7667712},\"jvm.memory.non-heap.max\":{\"value\":994050048},\"jvm.memory.non-heap.usage\":{\"value\":0.1523920694986979},\"jvm.memory.non-heap.used\":{\"value\":151485344},\"jvm.memory.pools.CodeHeap-'non-nmethods'.committed\":{\"value\":2555904},\"jvm.memory.pools.CodeHeap-'non-nmethods'.init\":{\"value\":2555904},\"jvm.memory.pools.CodeHeap-'non-nmethods'.max\":{\"value\":5832704},\"jvm.memory.pools.CodeHeap-'non-nmethods'.usage\":{\"value\":0.28408093398876405},\"jvm.memory.pools.CodeHeap-'non-nmethods'.used\":{\"value\":1656960},\"jvm.memory.pools.CodeHeap-'non-profiled-nmethods'.committed\":{\"value\":11796480},\"jvm.memory.pools.CodeHeap-'non-profiled-nmethods'.init\":{\"value\":2555904},\"jvm.memory.pools.CodeHeap-'non-profiled-nmethods'.max\":{\"value\":122912768},\"jvm.memory.pools.CodeHeap-'non-profiled-nmethods'.usage\":{\"value\":0.09536102872567315},\"jvm.memory.pools.CodeHeap-'non-profiled-nmethods'.used\":{\"value\":11721088},\"jvm.memory.pools.CodeHeap-'profiled-nmethods'.committed\":{\"value\":37355520},\"jvm.memory.pools.CodeHeap-'profiled-nmethods'.init\":{\"value\":2555904},\"jvm.memory.pools.CodeHeap-'profiled-nmethods'.max\":{\"value\":122912768},\"jvm.memory.pools.CodeHeap-'profiled-nmethods'.usage\":{\"value\":0.2538573047187417},\"jvm.memory.pools.CodeHeap-'profiled-nmethods'.used\":{\"value\":31202304},\"jvm.memory.pools.Compressed-Class-Space.committed\":{\"value\":14942208},\"jvm.memory.pools.Compressed-Class-Space.init\":{\"value\":0},\"jvm.memory.pools.Compressed-Class-Space.max\":{\"value\":367001600},\"jvm.memory.pools.Compressed-Class-Space.usage\":{\"value\":0.033858838762555805},\"jvm.memory.pools.Compressed-Class-Space.used\":{\"value\":12426248},\"jvm.memory.pools.Metaspace.committed\":{\"value\":99794944},\"jvm.memory.pools.Metaspace.init\":{\"value\":0},\"jvm.memory.pools.Metaspace.max\":{\"value\":375390208},\"jvm.memory.pools.Metaspace.usage\":{\"value\":0.25168142904782426},\"jvm.memory.pools.Metaspace.used\":{\"value\":94478744},\"jvm.memory.pools.PS-Eden-Space.committed\":{\"value\":349700096},\"jvm.memory.pools.PS-Eden-Space.init\":{\"value\":422576128},\"jvm.memory.pools.PS-Eden-Space.max\":{\"value\":1110966272},\"jvm.memory.pools.PS-Eden-Space.usage\":{\"value\":0.07505125052077188},\"jvm.memory.pools.PS-Eden-Space.used\":{\"value\":83379408},\"jvm.memory.pools.PS-Eden-Space.used-after-gc\":{\"value\":0},\"jvm.memory.pools.PS-Old-Gen.committed\":{\"value\":1127219200},\"jvm.memory.pools.PS-Old-Gen.init\":{\"value\":1127219200},\"jvm.memory.pools.PS-Old-Gen.max\":{\"value\":2253914112},\"jvm.memory.pools.PS-Old-Gen.usage\":{\"value\":0.014950035505168354},\"jvm.memory.pools.PS-Old-Gen.used\":{\"value\":33696096},\"jvm.memory.pools.PS-Old-Gen.used-after-gc\":{\"value\":23791152},\"jvm.memory.pools.PS-Survivor-Space.committed\":{\"value\":1048576},\"jvm.memory.pools.PS-Survivor-Space.init\":{\"value\":70254592},\"jvm.memory.pools.PS-Survivor-Space.max\":{\"value\":1048576},\"jvm.memory.pools.PS-Survivor-Space.usage\":{\"value\":0.59375},\"jvm.memory.pools.PS-Survivor-Space.used\":{\"value\":622592},\"jvm.memory.pools.PS-Survivor-Space.used-after-gc\":{\"value\":622592},\"jvm.memory.total.committed\":{\"value\":1644412928},\"jvm.memory.total.init\":{\"value\":1697972224},\"jvm.memory.total.max\":{\"value\":3999268864},\"jvm.memory.total.used\":{\"value\":269184904},\"jvm.thread.blocked.count\":{\"value\":0},\"jvm.thread.count\":{\"value\":82},\"jvm.thread.daemon.count\":{\"value\":11},\"jvm.thread.deadlock.count\":{\"value\":0},\"jvm.thread.deadlocks\":{\"value\":[]},\"jvm.thread.new.count\":{\"value\":0},\"jvm.thread.runnable.count\":{\"value\":25},\"jvm.thread.terminated.count\":{\"value\":0},\"jvm.thread.timed_waiting.count\":{\"value\":10},\"jvm.thread.waiting.count\":{\"value\":47}},\"counters\":{},\"histograms\":{},\"meters\":{},\"timers\":{}}\n\n$ curl -H 'Accept: application/prometheus' http://otoroshi-api.oto.tools:8080/metrics\\?access_key\\=MILpkVv6f2kG9Xmnc4mFIYRU4rTxHVGkxvB0hkQLZwEaZgE2hgbOXiRsN1DBnbtY\n# TYPE attr_jvm_cpu_usage gauge\nattr_jvm_cpu_usage 83.0\n# TYPE attr_jvm_heap_size gauge\nattr_jvm_heap_size 1409.0\n# TYPE attr_jvm_heap_used gauge\nattr_jvm_heap_used 220.0\n# TYPE internals_0_concurrent_requests gauge\ninternals_0_concurrent_requests 1.0\n# TYPE internals_global_throttling_quotas gauge\ninternals_global_throttling_quotas 3.0\n# TYPE jvm_attr_uptime gauge\njvm_attr_uptime 2372614.0\n# TYPE jvm_gc_PS_MarkSweep_count gauge\njvm_gc_PS_MarkSweep_count 3.0\n# TYPE jvm_gc_PS_MarkSweep_time gauge\njvm_gc_PS_MarkSweep_time 261.0\n# TYPE jvm_gc_PS_Scavenge_count gauge\njvm_gc_PS_Scavenge_count 12.0\n# TYPE jvm_gc_PS_Scavenge_time gauge\njvm_gc_PS_Scavenge_time 161.0\n# TYPE jvm_memory_heap_committed gauge\njvm_memory_heap_committed 1.477967872E9\n# TYPE jvm_memory_heap_init gauge\njvm_memory_heap_init 1.690304512E9\n# TYPE jvm_memory_heap_max gauge\njvm_memory_heap_max 3.005218816E9\n# TYPE jvm_memory_heap_usage gauge\njvm_memory_heap_usage 0.07680553268571043\n# TYPE jvm_memory_heap_used gauge\njvm_memory_heap_used 2.30817432E8\n# TYPE jvm_memory_non_heap_committed gauge\njvm_memory_non_heap_committed 1.66510592E8\n# TYPE jvm_memory_non_heap_init gauge\njvm_memory_non_heap_init 7667712.0\n# TYPE jvm_memory_non_heap_max gauge\njvm_memory_non_heap_max 9.94050048E8\n# TYPE jvm_memory_non_heap_usage gauge\njvm_memory_non_heap_usage 0.15262878997416435\n# TYPE jvm_memory_non_heap_used gauge\njvm_memory_non_heap_used 1.51720656E8\n# TYPE jvm_memory_pools_CodeHeap__non_nmethods__committed gauge\njvm_memory_pools_CodeHeap__non_nmethods__committed 2555904.0\n# TYPE jvm_memory_pools_CodeHeap__non_nmethods__init gauge\njvm_memory_pools_CodeHeap__non_nmethods__init 2555904.0\n# TYPE jvm_memory_pools_CodeHeap__non_nmethods__max gauge\njvm_memory_pools_CodeHeap__non_nmethods__max 5832704.0\n# TYPE jvm_memory_pools_CodeHeap__non_nmethods__usage gauge\njvm_memory_pools_CodeHeap__non_nmethods__usage 0.28408093398876405\n# TYPE jvm_memory_pools_CodeHeap__non_nmethods__used gauge\njvm_memory_pools_CodeHeap__non_nmethods__used 1656960.0\n# TYPE jvm_memory_pools_CodeHeap__non_profiled_nmethods__committed gauge\njvm_memory_pools_CodeHeap__non_profiled_nmethods__committed 1.1862016E7\n# TYPE jvm_memory_pools_CodeHeap__non_profiled_nmethods__init gauge\njvm_memory_pools_CodeHeap__non_profiled_nmethods__init 2555904.0\n# TYPE jvm_memory_pools_CodeHeap__non_profiled_nmethods__max gauge\njvm_memory_pools_CodeHeap__non_profiled_nmethods__max 1.22912768E8\n# TYPE jvm_memory_pools_CodeHeap__non_profiled_nmethods__usage gauge\njvm_memory_pools_CodeHeap__non_profiled_nmethods__usage 0.09610562183417755\n# TYPE jvm_memory_pools_CodeHeap__non_profiled_nmethods__used gauge\njvm_memory_pools_CodeHeap__non_profiled_nmethods__used 1.1812608E7\n# TYPE jvm_memory_pools_CodeHeap__profiled_nmethods__committed gauge\njvm_memory_pools_CodeHeap__profiled_nmethods__committed 3.735552E7\n# TYPE jvm_memory_pools_CodeHeap__profiled_nmethods__init gauge\njvm_memory_pools_CodeHeap__profiled_nmethods__init 2555904.0\n# TYPE jvm_memory_pools_CodeHeap__profiled_nmethods__max gauge\njvm_memory_pools_CodeHeap__profiled_nmethods__max 1.22912768E8\n# TYPE jvm_memory_pools_CodeHeap__profiled_nmethods__usage gauge\njvm_memory_pools_CodeHeap__profiled_nmethods__usage 0.25493618368435084\n# TYPE jvm_memory_pools_CodeHeap__profiled_nmethods__used gauge\njvm_memory_pools_CodeHeap__profiled_nmethods__used 3.1334912E7\n# TYPE jvm_memory_pools_Compressed_Class_Space_committed gauge\njvm_memory_pools_Compressed_Class_Space_committed 1.4942208E7\n# TYPE jvm_memory_pools_Compressed_Class_Space_init gauge\njvm_memory_pools_Compressed_Class_Space_init 0.0\n# TYPE jvm_memory_pools_Compressed_Class_Space_max gauge\njvm_memory_pools_Compressed_Class_Space_max 3.670016E8\n# TYPE jvm_memory_pools_Compressed_Class_Space_usage gauge\njvm_memory_pools_Compressed_Class_Space_usage 0.03386023385184152\n# TYPE jvm_memory_pools_Compressed_Class_Space_used gauge\njvm_memory_pools_Compressed_Class_Space_used 1.242676E7\n# TYPE jvm_memory_pools_Metaspace_committed gauge\njvm_memory_pools_Metaspace_committed 9.9794944E7\n# TYPE jvm_memory_pools_Metaspace_init gauge\njvm_memory_pools_Metaspace_init 0.0\n# TYPE jvm_memory_pools_Metaspace_max gauge\njvm_memory_pools_Metaspace_max 3.75390208E8\n# TYPE jvm_memory_pools_Metaspace_usage gauge\njvm_memory_pools_Metaspace_usage 0.25170985813247426\n# TYPE jvm_memory_pools_Metaspace_used gauge\njvm_memory_pools_Metaspace_used 9.4489416E7\n# TYPE jvm_memory_pools_PS_Eden_Space_committed gauge\njvm_memory_pools_PS_Eden_Space_committed 3.49700096E8\n# TYPE jvm_memory_pools_PS_Eden_Space_init gauge\njvm_memory_pools_PS_Eden_Space_init 4.22576128E8\n# TYPE jvm_memory_pools_PS_Eden_Space_max gauge\njvm_memory_pools_PS_Eden_Space_max 1.110966272E9\n# TYPE jvm_memory_pools_PS_Eden_Space_usage gauge\njvm_memory_pools_PS_Eden_Space_usage 0.17698545577448457\n# TYPE jvm_memory_pools_PS_Eden_Space_used gauge\njvm_memory_pools_PS_Eden_Space_used 1.96624872E8\n# TYPE jvm_memory_pools_PS_Eden_Space_used_after_gc gauge\njvm_memory_pools_PS_Eden_Space_used_after_gc 0.0\n# TYPE jvm_memory_pools_PS_Old_Gen_committed gauge\njvm_memory_pools_PS_Old_Gen_committed 1.1272192E9\n# TYPE jvm_memory_pools_PS_Old_Gen_init gauge\njvm_memory_pools_PS_Old_Gen_init 1.1272192E9\n# TYPE jvm_memory_pools_PS_Old_Gen_max gauge\njvm_memory_pools_PS_Old_Gen_max 2.253914112E9\n# TYPE jvm_memory_pools_PS_Old_Gen_usage gauge\njvm_memory_pools_PS_Old_Gen_usage 0.014950035505168354\n# TYPE jvm_memory_pools_PS_Old_Gen_used gauge\njvm_memory_pools_PS_Old_Gen_used 3.3696096E7\n# TYPE jvm_memory_pools_PS_Old_Gen_used_after_gc gauge\njvm_memory_pools_PS_Old_Gen_used_after_gc 2.3791152E7\n# TYPE jvm_memory_pools_PS_Survivor_Space_committed gauge\njvm_memory_pools_PS_Survivor_Space_committed 1048576.0\n# TYPE jvm_memory_pools_PS_Survivor_Space_init gauge\njvm_memory_pools_PS_Survivor_Space_init 7.0254592E7\n# TYPE jvm_memory_pools_PS_Survivor_Space_max gauge\njvm_memory_pools_PS_Survivor_Space_max 1048576.0\n# TYPE jvm_memory_pools_PS_Survivor_Space_usage gauge\njvm_memory_pools_PS_Survivor_Space_usage 0.59375\n# TYPE jvm_memory_pools_PS_Survivor_Space_used gauge\njvm_memory_pools_PS_Survivor_Space_used 622592.0\n# TYPE jvm_memory_pools_PS_Survivor_Space_used_after_gc gauge\njvm_memory_pools_PS_Survivor_Space_used_after_gc 622592.0\n# TYPE jvm_memory_total_committed gauge\njvm_memory_total_committed 1.644478464E9\n# TYPE jvm_memory_total_init gauge\njvm_memory_total_init 1.697972224E9\n# TYPE jvm_memory_total_max gauge\njvm_memory_total_max 3.999268864E9\n# TYPE jvm_memory_total_used gauge\njvm_memory_total_used 3.82665128E8\n# TYPE jvm_thread_blocked_count gauge\njvm_thread_blocked_count 0.0\n# TYPE jvm_thread_count gauge\njvm_thread_count 82.0\n# TYPE jvm_thread_daemon_count gauge\njvm_thread_daemon_count 11.0\n# TYPE jvm_thread_deadlock_count gauge\njvm_thread_deadlock_count 0.0\n# TYPE jvm_thread_new_count gauge\njvm_thread_new_count 0.0\n# TYPE jvm_thread_runnable_count gauge\njvm_thread_runnable_count 25.0\n# TYPE jvm_thread_terminated_count gauge\njvm_thread_terminated_count 0.0\n# TYPE jvm_thread_timed_waiting_count gauge\njvm_thread_timed_waiting_count 10.0\n# TYPE jvm_thread_waiting_count gauge\njvm_thread_waiting_count 47.0\n```"},{"name":"mtls.md","id":"/topics/mtls.md","url":"/topics/mtls.html","title":"Mutual TLS with Otoroshi","content":"# Mutual TLS with Otoroshi\n\nOtoroshi support mutual TLS out of the box. mTLS from client to Otoroshi and from Otoroshi to targets are supported. In this article we will see how to configure Otoroshi to use end-to-end mTLS. All code and files used in this articles can be found on the [Otoroshi github](https://github.com/MAIF/otoroshi/tree/master/demos/mtls)\n\n@@@ note { title=\"Experimental Feature\" }\nDynamic Mutual TLS is an experimental feature. It can change until it becomess an official feature\n@@@\n\n## End-to-end mTLS\n\nThe use case is the following :\n\n@@@ div { .centered-img }\n\n@@@\n\nfor this demo you will have to edit your `/etc/hosts` file to add the following entries\n\n```\n127.0.0.1 api.backend.lol api.frontend.lol www.backend.lol www.frontend.lol validation.backend.lol\n```\n\n### Create certificates\n\nBut first we need to generate some certificates to make the demo work\n\n```sh\nmkdir mtls-demo\ncd mtls-demo\nmkdir ca\nmkdir server\nmkdir client\n\n# create a certificate authority key, use password as pass phrase\nopenssl genrsa -out ./ca/ca-backend.key 4096\n# remove pass phrase\nopenssl rsa -in ./ca/ca-backend.key -out ./ca/ca-backend.key\n# generate the certificate authority cert\nopenssl req -new -x509 -sha256 -days 730 -key ./ca/ca-backend.key -out ./ca/ca-backend.cer -subj \"/CN=MTLSB\"\n\n\n# create a certificate authority key, use password as pass phrase\nopenssl genrsa -out ./ca/ca-frontend.key 2048\n# remove pass phrase\nopenssl rsa -in ./ca/ca-frontend.key -out ./ca/ca-frontend.key\n# generate the certificate authority cert\nopenssl req -new -x509 -sha256 -days 730 -key ./ca/ca-frontend.key -out ./ca/ca-frontend.cer -subj \"/CN=MTLSF\"\n\n\n# now create the backend cert key, use password as pass phrase\nopenssl genrsa -out ./server/_.backend.lol.key 2048\n# remove pass phrase\nopenssl rsa -in ./server/_.backend.lol.key -out ./server/_.backend.lol.key\n# generate the csr for the certificate\nopenssl req -new -key ./server/_.backend.lol.key -sha256 -out ./server/_.backend.lol.csr -subj \"/CN=*.backend.lol\"\n# generate the certificate\nopenssl x509 -req -days 365 -sha256 -in ./server/_.backend.lol.csr -CA ./ca/ca-backend.cer -CAkey ./ca/ca-backend.key -set_serial 1 -out ./server/_.backend.lol.cer\n# verify the certificate, should output './server/_.backend.lol.cer: OK'\nopenssl verify -CAfile ./ca/ca-backend.cer ./server/_.backend.lol.cer\n\n\n# now create the frontend cert key, use password as pass phrase\nopenssl genrsa -out ./server/_.frontend.lol.key 2048\n# remove pass phrase\nopenssl rsa -in ./server/_.frontend.lol.key -out ./server/_.frontend.lol.key\n# generate the csr for the certificate\nopenssl req -new -key ./server/_.frontend.lol.key -sha256 -out ./server/_.frontend.lol.csr -subj \"/CN=*.frontend.lol\"\n# generate the certificate\nopenssl x509 -req -days 365 -sha256 -in ./server/_.frontend.lol.csr -CA ./ca/ca-frontend.cer -CAkey ./ca/ca-frontend.key -set_serial 1 -out ./server/_.frontend.lol.cer\n# verify the certificate, should output './server/_.frontend.lol.cer: OK'\nopenssl verify -CAfile ./ca/ca-frontend.cer ./server/_.frontend.lol.cer\n\n\n# now create the client cert key for backend, use password as pass phrase\nopenssl genrsa -out ./client/_.backend.lol.key 2048\n# remove pass phrase\nopenssl rsa -in ./client/_.backend.lol.key -out ./client/_.backend.lol.key\n# generate the csr for the certificate\nopenssl req -new -key ./client/_.backend.lol.key -out ./client/_.backend.lol.csr -subj \"/CN=*.backend.lol\"\n# generate the certificate\nopenssl x509 -req -days 365 -sha256 -in ./client/_.backend.lol.csr -CA ./ca/ca-backend.cer -CAkey ./ca/ca-backend.key -set_serial 2 -out ./client/_.backend.lol.cer\n# generate a pkcs12 version of the cert and key, use password as password\nopenssl pkcs12 -export -clcerts -in client/_.backend.lol.cer -inkey client/_.backend.lol.key -out client/_.backend.lol.p12\n\n\n# now create the client cert key for frontend, use password as pass phrase\nopenssl genrsa -out ./client/_.frontend.lol.key 2048\n# remove pass phrase\nopenssl rsa -in ./client/_.frontend.lol.key -out ./client/_.frontend.lol.key\n# generate the csr for the certificate\nopenssl req -new -key ./client/_.frontend.lol.key -out ./client/_.frontend.lol.csr -subj \"/CN=*.frontend.lol\"\n# generate the certificate\nopenssl x509 -req -days 365 -sha256 -in ./client/_.frontend.lol.csr -CA ./ca/ca-frontend.cer -CAkey ./ca/ca-frontend.key -set_serial 2 -out ./client/_.frontend.lol.cer\n# generate a pkcs12 version of the cert and key, use password as password\nopenssl pkcs12 -export -clcerts -in client/_.frontend.lol.cer -inkey client/_.frontend.lol.key -out client/_.frontend.lol.p12\n```\n\nonce it's done, you should have something like\n\n```sh\n$ tree\n.\n├── backend.js\n├── ca\n│   ├── ca-backend.cer\n│   ├── ca-backend.key\n│   ├── ca-frontend.cer\n│   └── ca-frontend.key\n├── client\n│   ├── _.backend.lol.cer\n│   ├── _.backend.lol.csr\n│   ├── _.backend.lol.key\n│   ├── _.backend.lol.p12\n│   ├── _.frontend.lol.cer\n│   ├── _.frontend.lol.csr\n│   ├── _.frontend.lol.key\n│   └── _.frontend.lol.p12\n└── server\n ├── _.backend.lol.cer\n ├── _.backend.lol.csr\n ├── _.backend.lol.key\n ├── _.frontend.lol.cer\n ├── _.frontend.lol.csr\n └── _.frontend.lol.key\n\n3 directories, 18 files\n```\n\n### The backend service \n\nnow, let's create a backend service using nodejs. Create a file named `backend.js`\n\n```sh\ntouch backend.js\n```\n\nand put the following content\n\n```js\nconst fs = require('fs'); \nconst https = require('https'); \n\nconst options = { \n key: fs.readFileSync('./server/_.backend.lol.key'), \n cert: fs.readFileSync('./server/_.backend.lol.cer'), \n ca: fs.readFileSync('./ca/ca-backend.cer'), \n}; \n\nhttps.createServer(options, (req, res) => { \n res.writeHead(200, {\n 'Content-Type': 'application/json'\n }); \n res.end(JSON.stringify({ message: 'Hello World!' }) + \"\\n\"); \n}).listen(8444);\n```\n\nto run the server, just do \n\n```sh\nnode ./backend.js\n```\n\nnow you can try your server with\n\n```sh\ncurl --cacert ./ca/ca-backend.cer https://api.backend.lol:8444/\n# will print {\"message\":\"Hello World!\"}\n```\n\nnow modify your backend server to ensure that the client provides a client certificate like:\n\n```js\nconst fs = require('fs'); \nconst https = require('https'); \n\nconst options = { \n key: fs.readFileSync('./server/_.backend.lol.key'), \n cert: fs.readFileSync('./server/_.backend.lol.cer'), \n ca: fs.readFileSync('./ca/ca-backend.cer'), \n requestCert: true, \n rejectUnauthorized: true\n}; \n\nhttps.createServer(options, (req, res) => { \n console.log('Client certificate CN: ', req.socket.getPeerCertificate().subject.CN);\n res.writeHead(200, {\n 'Content-Type': 'application/json'\n }); \n res.end(JSON.stringify({ message: 'Hello World!' }) + \"\\n\"); \n}).listen(8444);\n```\n\nyou can test your new server with\n\n```sh\ncurl --cacert ./ca/ca-backend.cer --cert-type pkcs12 --cert ./client/_.backend.lol.p12:password https://api.backend.lol:8444/\n# will print {\"message\":\"Hello World!\"}\n```\n\n### Otoroshi setup\n\nDownload the latest version of the Otoroshi jar and run it like\n\n```sh\njava -jar otoroshi.jar\n\n[info] otoroshi-env - Admin API exposed on http://otoroshi-api.oto.tools:8080\n[info] otoroshi-env - Admin UI exposed on http://otoroshi.oto.tools:8080\n[info] otoroshi-in-memory-datastores - Now using InMemory DataStores\n[info] otoroshi-env - The main datastore seems to be empty, registering some basic services\n[info] otoroshi-env - You can log into the Otoroshi admin console with the following credentials: admin@otoroshi.io / xxxxxxxxxxxx\n[info] play.api.Play - Application started (Prod)\n[info] p.c.s.AkkaHttpServer - Listening for HTTP on /0:0:0:0:0:0:0:0:8080\n[info] p.c.s.AkkaHttpServer - Listening for HTTPS on /0:0:0:0:0:0:0:0:8443\n[info] otoroshi-env - Generating a self signed SSL certificate for https://*.oto.tools ...\n```\n\nand log into otoroshi with the tuple `admin@otoroshi.io / xxxxxxxxxxxx` displayed in the logs. Once logged in, create a new public service exposed on `http://api.frontend.lol` that targets `ahttps://api.backend.lol:8444/`.\n\n@@@ div { .centered-img }\n\n@@@\n\nand test it\n\n```sh\ncurl http://api.frontend.lol:8080/\n# the following error should be returned: {\"Otoroshi-Error\":\"Something went wrong, you should try later. Thanks for your understanding.\"}\n```\n\n@@@ warning\nAs seen before, the target of the otoroshi service is `ahttps://api.backend.lol:8444/`. `ahttps://` is not a typo and is intended. This tells otoroshi to use its experimental `http client` with dynamic tls support to fetch this resource.\n@@@\n\nyou should get an error due to the fact that Otoroshi doesn't know about the server certificate or the client certificate expected by the server.\n\nWe have to add the client certificate for `https://api.backend.lol` to Otoroshi. Go to http://otoroshi.oto.tools:8080/bo/dashboard/certificates and create a new item. Copy and paste the content of `./client/_.backend.lol.cer` and `./client/_.backend.lol.key` respectively in `Certificate full chain` and `Certificate private key`.\n\n@@@ div { .centered-img }\n\n@@@\n\nand retry the following curl command \n\n```sh\ncurl http://api.frontend.lol:8080/\n# the output should be: {\"message\":\"Hello World!\"}\n```\n\nnow we have to expose `https://api.frontend.lol:8443` using otoroshi. Go to http://otoroshi.oto.tools:8080/bo/dashboard/certificates and create a new item. Copy and paste the content of `./server/_.frontend.lol.cer` and `./server/_.frontend.lol.key` respectively in `Certificate full chain` and `Certificate private key`.\n\nand try the following command\n\n```sh\ncurl --cacert ./ca/ca-frontend.cer https://api.frontend.lol:8443/\n# the output should be: {\"message\":\"Hello World!\"}\n```\n\nnow we have to enforce the fact that we want client certificate for `api.frontend.lol`. To do that, we have to create a `Validation authority` in otoroshi and use it on the `api.frontend.lol` service. Go to http://otoroshi.oto.tools:8080/bo/dashboard/validation-authorities and create a new item. A validation authority is supposed to be a remote service that will say if the client certificate is valid. Here we don't really care if the certificate is valid or not, but we want to enforce the fact that there is a client certificate. So just check the `All cert. valid button`.\n\n@@@ div { .centered-img }\n\n@@@\n\nnow go back on your `api.frontend.lol` service, in the `Validation authority` section and select the authority you just created.\n\n@@@ div { .centered-img }\n\n@@@\n\nnow if you retry \n\n```sh\ncurl --cacert ./ca/ca-frontend.cer https://api.frontend.lol:8443/\n# the output should be: {\"Otoroshi-Error\":\"You're not authorized here !\"}\n```\n\nyou should get an error because no client cert. is passed with the request. But if you pass the `./client/_.frontend.lol.p12` client cert in your curl call\n\n```sh\ncurl --cacert ./ca/ca-frontend.cer --cert-type pkcs12 --cert ./client/_.frontend.lol.p12:password https://api.frontend.lol:8443/\n# the output should be: {\"message\":\"Hello World!\"}\n```\n\n### End to end test\n\nNow we can try to write a small nodejs client that uses our client certificates. Create a `client.js` file with the following code\n\n```js\nconst fs = require('fs'); \nconst https = require('https'); \n\nprocess.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0;\n\nconst options = { \n hostname: 'api.frontend.lol', \n port: 8443, \n path: '/', \n method: 'GET', \n key: fs.readFileSync('./client/_.frontend.lol.key'), \n cert: fs.readFileSync('./client/_.frontend.lol.cer'), \n ca: fs.readFileSync('./ca/ca-frontend.cer'), \n}; \n\nconst req = https.request(options, (res) => { \n console.log('statusCode', res.statusCode);\n console.log('headers', res.headers);\n console.log('body:');\n res.on('data', (data) => { \n process.stdout.write(data); \n }); \n}); \n\nreq.end(); \n\nreq.on('error', (e) => { \n console.error(e); \n});\n```\n\nand run the following command\n\n```sh\n$ node client.js\n# statusCode 200\n# headers { date: 'Mon, 10 Dec 2018 16:01:11 GMT',\n# connection: 'close',\n# 'transfer-encoding': 'chunked',\n# 'content-type': 'application/json' }\n# body:\n# {\"message\":\"Hello World!\"}\n```\n\nAnd that's it \n\n## Validating client certificates based on user identity\n\n@@@ note { title=\"Experimental Feature\" }\nValidation authorities is an experimental feature. It can change until it becomess an official feature\n@@@\n\nThe use case is the following :\n\n@@@ div { .centered-img }\n\n@@@\n\nthe idea here is to provide a unique client certificate per device that can access Otoroshi and use a validation authority to check if the user is allowed to access the underlying app with a specific device.\n\n### Generate client certificates for devices\n\nTo do that we are going to create two client certificates, one per device (let say for a laptop and a desktop computer). We are going to use the device serial number as common name of the certificate to be able to identify the device behind the certificate.\n\n```sh\nopenssl genrsa -out ./client/device-1.key 2048\nopenssl rsa -in ./client/device-1.key -out ./client/device-1.key\nopenssl req -new -key ./client/device-1.key -out ./client/device-1.csr -subj \"/CN=mbp-123456789\"\nopenssl x509 -req -days 365 -sha256 -in ./client/device-1.csr -CA ./ca/ca-frontend.cer -CAkey ./ca/ca-frontend.key -set_serial 3 -out ./client/device-1\nopenssl pkcs12 -export -clcerts -in client/device-1 -inkey client/device-1.key -out client/device-1.p12\n\nopenssl genrsa -out ./client/device-2.key 2048\nopenssl rsa -in ./client/device-2.key -out ./client/device-2.key\nopenssl req -new -key ./client/device-2.key -out ./client/device-2.csr -subj \"/CN=nuc-987654321\"\nopenssl x509 -req -days 365 -sha256 -in ./client/device-2.csr -CA ./ca/ca-frontend.cer -CAkey ./ca/ca-frontend.key -set_serial 4 -out ./client/device-2\nopenssl pkcs12 -export -clcerts -in client/device-2 -inkey client/device-2.key -out client/device-2.p12\n```\n\n### Setup actual validation\n\nnow we are going to write an validation authority (with mTLS too) that is going to respond on `https://validation.backend.lol:8445`. The server has access to a list of apps, users and devices to check if everything is correct. In this implementation, the lists are hardcoded, but you can write your own implementation that will fetch data from your corporate LDAP, CA, etc. Create a `validation.js` file and add the following content. Don't forget to do `yarn add x509` before running the server with `node validation.js`\n\n```js\nconst fs = require('fs'); \nconst https = require('https'); \nconst x509 = require('x509');\n\n// list of knwon apps\nconst apps = [\n {\n \"id\": \"iogOIDH09EktFhydTp8xspGvdaBq961DUDr6MBBNwHO2EiBMlOdafGnImhbRGy8z\",\n \"name\": \"my-web-service\",\n \"description\": \"A service that says hello\",\n \"host\": \"www.frontend.lol\"\n }\n];\n\n// list of known users\nconst users = [\n {\n \"name\": \"Mathieu\",\n \"email\": \"mathieu@oto.tools\",\n \"appRights\": [\n {\n \"id\": \"iogOIDH09EktFhydTp8xspGvdaBq961DUDr6MBBNwHO2EiBMlOdafGnImhbRGy8z\",\n \"profile\": \"user\",\n \"forbidden\": false\n },\n {\n \"id\": \"PqgOIDH09EktFhydTp8xspGvdaBq961DUDr6MBBNwHO2EiBMlOdafGnImhbRGy8z\",\n \"profile\": \"none\",\n \"forbidden\": true\n },\n ],\n \"ownedDevices\": [\n \"mbp-123456789\",\n \"nuc-987654321\",\n ]\n }\n];\n\n// list of known devices\nconst devices = [\n {\n \"serialNumber\": \"mbp-123456789\",\n \"hardware\": \"Macbook Pro 2018 13 inc. with TouchBar, 2.6 GHz, 16 Gb\",\n \"acquiredAt\": \"2018-10-01\",\n },\n {\n \"serialNumber\": \"nuc-987654321\",\n \"hardware\": \"Intel NUC i7 3.0 GHz, 32 Gb\",\n \"acquiredAt\": \"2018-09-01\",\n },\n {\n \"serialNumber\": \"iphone-1234\",\n \"hardware\": \"Iphone XS, 256 Gb\",\n \"acquiredAt\": \"2018-12-01\",\n }\n];\n\nconst options = { \n key: fs.readFileSync('./server/_.backend.lol.key'), \n cert: fs.readFileSync('./server/_.backend.lol.cer'), \n ca: fs.readFileSync('./ca/ca-backend.cer'), \n requestCert: true, \n rejectUnauthorized: true\n}; \n\nfunction readBody(request) {\n return new Promise((success, failure) => {\n const body = [];\n request.on('data', (chunk) => {\n body.push(chunk);\n }).on('end', () => {\n const bodyStr = Buffer.concat(body).toString();\n success(JSON.parse(bodyStr));\n });\n });\n}\n\nfunction chainIsValid(chain) {\n // validate cert dates\n // validate cert against clr\n // validate whatever you want here\n return true;\n}\n\nfunction call(req, res) {\n readBody(req).then(body => {\n const service = body.service;\n const email = (body.user || { email: 'mathieu@oto.tools' }).email; // here, should not be null if used with an otoroshi auth. module\n // common name should be device serial number\n const commonName = x509.getSubject(body.chain).commonName\n // search for a known device\n const device = devices.filter(d => d.serialNumber === commonName)[0];\n // search for a known user\n const user = users.filter(d => d.email === email)[0];\n // search for a known application\n const app = apps.filter(d => d.id === service.id)[0];\n res.writeHead(200, { 'Content-Type': 'application/json' }); \n if (chainIsValid(body.chain.map(x509.parseCert)) && user && device && app) {\n // check if the user actually owns the device\n const userOwnsDevice = user.ownedDevices.filter(d => d === device.serialNumber)[0];\n // check if the user has rights to access the app\n const rights = user.appRights.filter(d => d.id === app.id)[0];\n const hasRightToUseApp = !rights.forbidden\n if (userOwnsDevice && hasRightToUseApp) {\n // yeah !!!!\n console.log(`Call from user \"${user.email}\" with device \"${device.hardware}\" on app \"${app.name}\" with profile \"${rights.profile}\" authorized`)\n res.end(JSON.stringify({ status: 'good', profile: rights.profile }) + \"\\n\"); \n } else {\n // nope !!! nope, nope nope\n console.log(`Call from user \"${user.email}\" with device \"${device.hardware}\" on app \"${app.name}\" unauthorized because user doesn't owns the hardware or has no rights`)\n res.end(JSON.stringify({ status: 'unauthorized' }) + \"\\n\"); \n }\n } else {\n console.log(`Call unauthorized`)\n res.end(JSON.stringify({ status: 'unauthorized' }) + \"\\n\"); \n }\n });\n}\n\nhttps.createServer(options, call).listen(8445);\n```\n\nthe corresponding authority validation can be created in Otoroshi like \n\n```json\n{\n \"id\": \"r7m8j31rh66hhdia3ormfm0wfevu1kvg0zgaxsp3oxb6ivf7fy8kvygmvnrlxv81\",\n \"name\": \"Actual validation authority\",\n \"description\": \"Actual validation authority\",\n \"url\": \"ahttps://validation.backend.lol:8445\",\n \"host\": \"validation.backend.lol\",\n \"goodTtl\": 600000,\n \"badTtl\": 60000,\n \"method\": \"POST\",\n \"path\": \"/certificates/_validate\",\n \"timeout\": 10000,\n \"noCache\": false,\n \"alwaysValid\": false,\n \"headers\": {}\n}\n```\n\nbut you don't need to create it right now.\n\nTypically, a validation authority server is a server with a route on `POST /certificates/_validate` that accepts `application/json` and returns `application/json` with a body like\n\n```json\n{\n \"apikey\": nullable {\n \"clientId\": String,\n \"clientName\": String,\n \"authorizedGroup\": String,\n \"enabled\": Boolean,\n \"readOnly\": Boolean,\n \"allowClientIdOnly\": Boolean,\n \"throttlingQuota\": Long,\n \"dailyQuota\": Long,\n \"monthlyQuota\": Long,\n \"metadata\": Map[String, String]\n },\n \"user\": nullable {\n \"email\": String,\n \"name\": String,\n },\n \"service\": {\n \"id\": String,\n \"name\": String,\n \"groupId\": String,\n \"domain\": String,\n \"env\": String,\n \"subdomain\": String,\n \"root\": String,\n \"metadata\": String\n },\n \"chain\": PemFormattedCertificateChainString,\n \"fingerprints\": Array[String]\n}\n```\n\n\n### Setup Otoroshi\n\nYou can start Otoroshi and import data from the `state.json` file in the demo folder. The login tuple is `admin@otoroshi.io / password`. The `state.json` file contains everything you need for the demo, like certificates, service descriptors, auth. modules, etc ...\n\n```sh\njava -Dapp.importFrom=$(pwd)/state.json -Dapp.privateapps.port=8080 -jar otoroshi.jar\n\n[info] otoroshi-env - Admin API exposed on http://otoroshi-api.oto.tools:8080\n[info] otoroshi-env - Admin UI exposed on http://otoroshi.oto.tools:8080\n[info] otoroshi-in-memory-datastores - Now using InMemory DataStores\n[info] otoroshi-env - The main datastore seems to be empty, registering some basic services\n[info] otoroshi-env - Importing from: /pwd/state.json\n[info] play.api.Play - Application started (Prod)\n[info] otoroshi-env - Successful import !\n[info] p.c.s.AkkaHttpServer - Listening for HTTP on /0:0:0:0:0:0:0:0:8080\n[info] p.c.s.AkkaHttpServer - Listening for HTTPS on /0:0:0:0:0:0:0:0:8443\n```\n\n### Testing \n\nYou can test the service with curl like\n\n```sh\ncurl --cacert ./ca/ca-frontend.cer --cert-type pkcs12 --cert ./client/device-1.p12:password https://www.frontend.lol:8443/\n# output:

Hello World !!!

\ncurl --cacert ./ca/ca-frontend.cer --cert-type pkcs12 --cert ./client/device-2.p12:password https://www.frontend.lol:8443/\n# output:

Hello World !!!

\ncurl --cacert ./ca/ca-frontend.cer --cert-type pkcs12 --cert ./client/_.frontend.lol.p12:password https://www.frontend.lol:8443/\n# output: {\"Otoroshi-Error\":\"You're not authorized here !\"}\n```\n\nas expected, the first two call works as their common name is known by the validation server. The last one fails as it's not known.\n\n### Validate user identity\n\nNow let's try to setup firefox to provide the client certificate. Open firefox settings, go to `privacy settings and security` and click on `display certificates` at the bottom of the page. Here you can add the frontend CA (`./ca/ca-frontend.cer`) in the `Authorities` tab, check the 'authorize this CA to identify websites', and then in the `certificates` tab, import one of the devices `.p12` file (like `./client/device-1.p12`). Firefox will ask for the files password (it should be `password`).\n\n@@@ div { .centered-img }\n\n@@@\n\nNow restart firefox.\n\nNext, go to the `my-web-service` service in otoroshi (log in with `admin@otoroshi.io / password`) and activate `Enforce user login` in the Authentication section. It means that now, you'll have to log in when you'll go to https://www.frontend.lol:8443. With authentication activated on otoroshi, the user identity will be sent to the validation authority, so you can change the following line in the file `validation.js`\n\n```js\nconst email = (body.user || { email: 'mathieu@oto.tools' }).email; // here, should not be null if used with an otoroshi auth. module\n```\n\nto\n\n```js\nconst email = body.user.email;\n```\n\nThen, in Firefox, go to https://www.frontend.lol:8443/, firefox will ask which client certificate to use. Select the one you imported (in the process, maybe firefox will warn you that the certificate of the site is auto signed, just ignore it and continue ;) )\n\n@@@ div { .centered-img }\n\n@@@\n\nthen, you'll see a login screen from otoroshi. You can log in with `mathieu@oto.tools / password` and then you should see the hello world message.\n\n@@@ div { .centered-img }\n\n@@@\n\n### Going further with user authentication\n\nFor stronger user authentication, you can try to use an auth. module baked by a keycloak instance with yubikey as a strong second factor authentication instead of the basic auth. module we used previously in this article.\n"},{"name":"req-transformers.md","id":"/topics/req-transformers.md","url":"/topics/req-transformers.html","title":"Request transformers","content":"# Request transformers\n\nWhen everything has failed and you absolutely need a feature in Otoroshi to make your use case work, there is a solution. Request transformer is an experimental feature hidden behind a feature flag that allow you to code how Otoroshi should behave when receiving and rounting an http request. With request transformers, you can change request / response headers and request / response body to way you want.\n\n@@@ note { title=\"Experimental Feature\" }\nRequest transformers is an experimental feature. It can change until it becomess an official feature\n@@@\n\n@@@ warning { title=\"Use at your own risk\" }\nRequest transformers is a **very** powerful feature that allow you to do almost everything you want. But like any powerful feature, it comes with a price. The fact that you are responsible for how the request is transformed makes you responsible for all issues introduced by the transformer. If you block the current thread, introduce big latency, generate uncatched exceptions, etc. it's **your fault**. You have to ensure that your code is fast, non blocking, safe, etc. In any case, Otoroshi developers will not responsible for issues caused by a request transformer.\n\nAnd always remember this quote from ~~uncle Ben~~ Winston Churchill:\n\n

\"Where there is great power there is great responsibility\"

\n@@@\n\n## Enabling request transformers\n\nFirst you have to enable Otoroshi request transformers with the `-Dotoroshi.scripts.enabled=true` flag, or using env. variable `OTOROSHI_SCRIPTS_ENABLED=true`.\n\n## Anatomy of a transformer\n\nA request transformer is a piece of Scala code than can handle the complete lifecycle of an http request made on Otoroshi\n\n```scala\ncase class HttpRequest(url: String, method: String, headers: Map[String, String], query: Map[String, String], cookies: Seq[WSCookie] = Seq.empty[WSCookie]) {\n lazy val host: String = headers.getOrElse(\"Host\", \"\")\n lazy val uri: Uri = Uri(url)\n lazy val scheme: String = uri.scheme\n lazy val authority: Uri.Authority = uri.authority\n lazy val fragment: Option[String] = uri.fragment\n lazy val path: String = uri.path.toString()\n lazy val queryString: Option[String] = uri.rawQueryString\n lazy val relativeUri: String = uri.toRelative.toString()\n}\ncase class HttpResponse(status: Int, headers: Map[String, String], cookies: Seq[WSCookie] = Seq.empty[WSCookie])\n\ntrait RequestTransformer {\n\n /**\n * See RequestTransformer.transformRequest\n */\n def transformRequestSync(\n snowflake: String, // a cluster unique request id\n rawRequest: HttpRequest, // the http request as received by Otoroshi\n otoroshiRequest: HttpRequest, // the http request modified by Otoroshi, ready to be sent to the target service\n desc: ServiceDescriptor, // the service description for the request \n apiKey: Option[ApiKey] = None, // the api key used, if one\n user: Option[PrivateAppsUser] = None // the user, if one\n )(implicit \n env: Env, // the otoroshi environnment\n ec: ExecutionContext, // the threadpool for the request to perform async actions\n mat: Materializer // the akka materializer to work with streams\n ): Either[Result, HttpRequest] = {\n Right(otoroshiRequest)\n }\n\n /**\n * Transform the request forwarded from Otoroshi to the target service\n */\n def transformRequest(\n snowflake: String, // a cluster unique request id\n rawRequest: HttpRequest, // the http request as received by Otoroshi\n otoroshiRequest: HttpRequest, // the http request modified by Otoroshi, ready to be sent to the target service\n desc: ServiceDescriptor, // the service description for the request \n apiKey: Option[ApiKey] = None, // the api key used, if one\n user: Option[PrivateAppsUser] = None // the user, if one\n )(implicit \n env: Env, // the otoroshi environnment\n ec: ExecutionContext, // the threadpool for the request to perform async actions\n mat: Materializer // the akka materializer to work with streams\n ): Future[Either[Result, HttpRequest]] = { // return a future of Either. On the left side, an http error if there was en err. On the Right side, the transformed http request\n FastFuture.successful(transformRequestSync(snowflake, rawRequest, otoroshiRequest, desc, apiKey, user)(env, ec, mat))\n }\n\n /**\n * See RequestTransformer.transformResponse\n */\n def transformResponseSync(\n snowflake: String, // a cluster unique request id\n rawResponse: HttpResponse, // the http response as received by Otoroshi from the target service\n otoroshiResponse: HttpResponse, // the http response modified by Otoroshi, ready to be sent back to the client\n desc: ServiceDescriptor, // the service description for the request \n apiKey: Option[ApiKey] = None, // the api key used, if one\n user: Option[PrivateAppsUser] = None // the user, if one\n )(implicit \n env: Env, // the otoroshi environnment\n ec: ExecutionContext, // the threadpool for the request to perform async actions\n mat: Materializer // the akka materializer to work with streams\n ): Either[Result, HttpResponse] = {\n Right(otoroshiResponse)\n }\n\n /**\n * Transform the response from the target to the client\n */ \n def transformResponse(\n snowflake: String, // a cluster unique request id\n rawResponse: HttpResponse, // the http response as received by Otoroshi from the target service\n otoroshiResponse: HttpResponse, // the http response modified by Otoroshi, ready to be sent back to the client\n desc: ServiceDescriptor, // the service description for the request \n apiKey: Option[ApiKey] = None, // the api key used, if one\n user: Option[PrivateAppsUser] = None // the user, if one\n )(implicit \n env: Env, // the otoroshi environnment\n ec: ExecutionContext, // the threadpool for the request to perform async actions\n mat: Materializer // the akka materializer to work with streams\n ): Future[Either[Result, HttpResponse]] = { // return a future of Either. On the left side, an http error if there was en err. On the Right side, the transformed http response\n FastFuture.successful(transformResponseSync(snowflake, rawResponse, otoroshiResponse, desc, apiKey, user)(env, ec, mat))\n }\n\n /**\n * Transform the request body forwarded from Otoroshi to the target service\n * It uses akka-stream (see https://doc.akka.io/docs/akka/2.5/index.html)\n */\n def transformRequestBody(\n snowflake: String, // a cluster unique request id\n body: Source[ByteString, _], // the body of the request, a stream of byte array\n rawRequest: HttpRequest, // the http request as received by Otoroshi\n otoroshiRequest: HttpRequest, // the http request modified by Otoroshi, ready to be sent to the target service\n desc: ServiceDescriptor, // the service description for the request \n apiKey: Option[ApiKey] = None, // the api key used, if one\n user: Option[PrivateAppsUser] = None // the user, if one\n )(implicit \n env: Env, // the otoroshi environnment\n ec: ExecutionContext, // the threadpool for the request to perform async actions\n mat: Materializer // the akka materializer to work with streams\n ): Source[ByteString, _] = { // return a stream of byte array\n body\n }\n\n /**\n * Transform the response body forwarded from target to the client. \n * It uses akka-stream (see https://doc.akka.io/docs/akka/2.5/index.html)\n */\n def transformResponseBody(\n snowflake: String, // a cluster unique request id\n body: Source[ByteString, _], // the body of the response, a stream of byte array\n rawResponse: HttpResponse, // the http response as received by Otoroshi from the target service\n otoroshiResponse: HttpResponse, // the http response modified by Otoroshi, ready to be sent back to the client\n desc: ServiceDescriptor, // the service description for the request \n apiKey: Option[ApiKey] = None, // the api key used, if one\n user: Option[PrivateAppsUser] = None // the user, if one\n )(implicit \n env: Env, // the otoroshi environnment\n ec: ExecutionContext, // the threadpool for the request to perform async actions\n mat: Materializer // the akka materializer to work with streams\n ): Source[ByteString, _] = {\n body\n }\n}\n```\n\nfor more information about APIs you can use\n\n* https://www.playframework.com/documentation/2.6.x/api/scala/index.html#package\n* https://www.playframework.com/documentation/2.6.x/api/scala/index.html#play.api.mvc.Results\n* https://github.com/MAIF/otoroshi\n* https://doc.akka.io/docs/akka/2.5/stream/index.html\n* https://doc.akka.io/api/akka/current/akka/stream/index.html\n* https://doc.akka.io/api/akka/current/akka/stream/scaladsl/Source.html\n\n## Writing a transformer from Otoroshi UI\n\nLog into Otoroshi and go to `Settings (cog icon) / Scripts`. Here you can create multiple request transformer scripts and associate it with service descriptor later.\n\n@@@ div { .centered-img }\n\n@@@\n\nwhen you write an instance of a transformer in the Otoroshi UI, do the following\n\n```scala\nimport akka.stream.Materializer\nimport env.Env\nimport models.{ApiKey, PrivateAppsUser, ServiceDescriptor}\nimport otoroshi.script._\nimport play.api.Logger\nimport play.api.mvc.{Result, Results}\nimport scala.util._\nimport scala.concurrent.{ExecutionContext, Future}\n\nclass MyTransformer extends RequestTransformer {\n\n val logger = Logger(\"my-transformer\")\n\n // implements the methods you want\n}\n\n// WARN: do not forget this line to provide a working instance of your transformer to Otoroshi\nnew MyTransformer()\n```\n\nYou can use the compile button to check if the script compiles, or code the transformer in your IDE (see next point).\n\nThen go to a service descriptor, scroll to the bottom of the page, and select your transformer in the list\n\n@@@ div { .centered-img }\n\n@@@\n\n## Providing a transformer from Java classpath\n\nYou can write your own transformer using your favorite IDE. Just create an SBT project with the following dependencies. It can be quite handy to manage the source code like any other piece of code, and it avoid the compilation time for the script at Otoroshi startup.\n\n```scala\nlazy val root = (project in file(\".\")).\n settings(\n inThisBuild(List(\n organization := \"com.example\",\n scalaVersion := \"2.12.7\",\n version := \"0.1.0-SNAPSHOT\"\n )),\n name := \"request-transformer-example\",\n resolvers += Resolver.bintrayRepo(\"maif\", \"maven\"),\n libraryDependencies += \"fr.maif.otoroshi\" %% \"otoroshi\" % \"1.x.x\"\n )\n```\n\nWhen your code is ready, create a jar file \n\n```\nsbt package\n```\n\nand add the jar file to the Otoroshi classpath\n\n```sh\njava -Dotoroshi.scripts.enabled=true -cp \"/path/to/transformer.jar:$/path/to/otoroshi.jar\" play.core.server.ProdServerStart\n```\n\nthen, in your service descriptor, you can chose your transformer in the list. If you want to do it from the API, you have to defined the transformerRef using `cp:` prefix like \n\n```json\n{\n \"transformerRef\": \"cp:my.class.package.MyTransformer\"\n}\n```\n\n## Getting custom configuration from the Otoroshi config. file\n\nLet say you need to provide custom configuration values for a script, then you can customize a configuration file of Otoroshi\n\n```hocon\ninclude \"application.conf\"\n\notoroshi {\n scripts {\n enabled = true\n }\n}\n\nmy-transformer {\n env = \"prod\"\n maxRequestBodySize = 2048\n maxResponseBodySize = 2048\n}\n```\n\nthen start Otoroshi like\n\n```sh\njava -Dconfig.file=/path/to/custom.conf -jar otoroshi.jar\n```\n\nthen, in your transformer, you can write something like \n\n```scala\npackage com.example.otoroshi\n\nimport akka.stream.Materializer\nimport akka.stream.scaladsl._\nimport akka.util.ByteString\nimport env.Env\nimport models.{ApiKey, PrivateAppsUser, ServiceDescriptor}\nimport otoroshi.script._\nimport play.api.Logger\nimport play.api.mvc.{Result, Results}\nimport scala.util._\nimport scala.concurrent.{ExecutionContext, Future}\n\nclass BodyLengthLimiter extends RequestTransformer {\n\n override def transformResponseBody(\n snowflake: String,\n body: Source[ByteString, _],\n rawResponse: HttpResponse,\n otoroshiResponse: HttpResponse,\n desc: ServiceDescriptor,\n apiKey: Option[ApiKey],\n user: Option[PrivateAppsUser])(implicit env: Env, ec: ExecutionContext, mat: Materializer): Source[ByteString, _] = {\n val max = env.configuration.getOptional[Long](\"my-transformer.maxResponseBodySize\").getOrElse(Long.MaxValue)\n body.limitWeighted(max)(_.size)\n }\n\n override def transformRequestBody(\n snowflake: String,\n body: Source[ByteString, _],\n rawRequest: HttpRequest,\n otoroshiRequest: HttpRequest,\n desc: ServiceDescriptor,\n apiKey: Option[ApiKey],\n user: Option[PrivateAppsUser])(implicit env: Env, ec: ExecutionContext, mat: Materializer): Source[ByteString, _] = {\n val max = env.configuration.getOptional[Long](\"my-transformer.maxRequestBodySize\").getOrElse(Long.MaxValue)\n body.limitWeighted(max)(_.size)\n }\n}\n```\n\n## Using a library that is not embedded in Otoroshi\n\nJust use the `classpath` option when running Otoroshi\n\n```sh\njava -Dotoroshi.scripts.enabled=true -cp \"/path/to/library.jar:$/path/to/otoroshi.jar\" play.core.server.ProdServerStart\n```\n\nBe carefull as your library can conflict with other libraries used by Otoroshi and affect its stability\n"},{"name":"service-mesh.md","id":"/topics/service-mesh.md","url":"/topics/service-mesh.html","title":"Service mesh with Otoroshi","content":"# Service mesh with Otoroshi\n\n[Service mesh](http://philcalcado.com/2017/08/03/pattern_service_mesh.html) is a pattern that gained a lot of traction lately with tools like [Kubernetes](https://kubernetes.io/) and [Istio](https://istio.io/) using patterns like [sidecar container](https://kubernetes.io/blog/2015/06/the-distributed-system-toolkit-patterns/#example-1-sidecar-containers). It has the advantages to move the complexity of calling other services outside the application. For the application, it's just a local call and the proxy handles apikeys, throttling, quotas, resilience, tracing, etc ...\n\n## Architecture\n\nLet's say that we have 4 service that want to talk to each other. But we don't want to handle the complexity of calling those services with their own apikey, etc ...\n\n@@@ div { .centered-img }\n\n@@@\n\nSo we are going to build an infrastructure where each service has a sidecar Otoroshi that will handle the calls to other services\n\n@@@ div { .centered-img }\n\n@@@\n\n## Configuration\n\nIn this infrastructure, each service has its own service descriptor in Otoroshi that points to another Otoroshi instance. The challenge here is to dynamically change the target of any service when the current call is supposed to access th actual app.\n\nTo do that, each Otoroshi instance (that is part of the same cluster and use the same database) will have a configuration slightly different configuration than the other ones. \n\n```hocon\nsidecar {\n serviceId = \"my-local-service-id\" # the id of the local sidecar app\n target = \"http://127.0.0.1:56876\" # the local service target, it should be on 127.0.0.1 but can be on another ip address\n from = \"127.0.0.1\" # the ip address authorized to actualy access the local app, it should be on 127.0.0.1 but can be on another ip address\n strict = true # use actual remote address or remote address/proxied remote address\n apikey {\n clientId = \"my-apikey-id\" # the apikey client id to access other services\n }\n}\n```\n\n## Configuration using env. variables\n\nYou can also only env. variables to configure sidecar Otoroshi instances \n\n```sh\nSIDECAR_SERVICE_ID=my-local-service-id\nSIDECAR_TARGET=http://127.0.0.1:56876\nSIDECAR_FROM=127.0.0.1\nSIDECAR_STRICT=true\nSIDECAR_APIKEY_CLIENT_ID=my-apikey-id\n```\n\n## Example\n\nYou can find a full example of a simple service mesh using Otoroshi [here](https://github.com/MAIF/otoroshi/tree/master/demos/service-mesh)."},{"name":"snow-monkey.md","id":"/topics/snow-monkey.md","url":"/topics/snow-monkey.html","title":"Chaos engineering with the Snow Monkey","content":"# Chaos engineering with the Snow Monkey\n\nNihonzaru (the Snow Monkey) is the chaos engineering tool provided by Otoroshi. You can access it at `Settings (cog icon) / Snow Monkey`.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Chaos engineering\n\nOtoroshi offers some tools to introduce [chaos engineering](https://principlesofchaos.org/) in your everyday life. With chaos engineering, you will improve the resilience of your architecture by creating faults in production on running systems. With [Nihonzaru (the snow monkey)](https://en.wikipedia.org/wiki/Japanese_macaque) Otoroshi helps you to create faults on http request/response handled by Otoroshi. \n\n@@@ div { .centered-img }\n\n@@@\n\n## Settings\n\n@@@ div { .centered-img }\n\n@@@\n\nThe snow monkey let you define a few settings to work properly :\n\n* **Include user facing apps.**: you want to create fault in production, but maybe you don't want your users to enjoy some nice snow monkey generated error pages. Using this switch let you include of not user facing apps (ui apps). Each service descriptor has a `User facing app switch` that will be used by the snow monkey.\n* **Dry run**: when dry run is enabled, outages will be registered and will generate events and alerts (in the otoroshi eventing system) but requests won't be actualy impacted. It's a good way to prepare applications to the snow monkey habits\n* **Outage strategy**: Either `AllServicesPerGroup` or `OneServicePerGroup`. It means that only one service per group or all services per groups will have `n` outages (see next bullet point) during the snow monkey working period\n* **Outages per day**: during snow monkey working period, each service per group or one service per group will have only `n` outages registered \n* **Working period**: the snow monkey only works during a working period. Here you can defined when it starts and when it stops\n* **Outage duration**: here you can defined the bounds for the random outage duration when an outage is created on a service\n* **Impacted groups**: here you can define a list of service groups impacted by the snow monkey. If none is specified, then all service groups will be impacted\n\n## Faults\n\nWith the snow monkey, you can generate four types of faults\n\n* **Large request fault**: Add trailing bytes at the end of the request body (if one)\n* **Large response fault**: Add trailing bytes at the end of the response body\n* **Latency injection fault**: Add random response latency between two bounds\n* **Bad response injection fault**: Create predefined responses with custom headers, body and status code\n\nEach fault let you define a ratio for impacted requests. If you specify a ratio of `0.2`, then 20% of the requests for the impacte service will be impacted by this fault\n\n@@@ div { .centered-img }\n\n@@@\n\nThen you juste have to start the snow monkey and enjoy the show ;)\n\n@@@ div { .centered-img }\n\n@@@\n\n## Current outages\n\nIn the last section of the snow monkey page, you can see current outages (per service), when they started, their duration, etc ...\n\n@@@ div { .centered-img }\n\n@@@"},{"name":"ssl.md","id":"/topics/ssl.md","url":"/topics/ssl.html","title":"SSL/TLS termination with Otoroshi","content":"# SSL/TLS termination with Otoroshi\n\nOtoroshi can be used as an SSL/TLS termination. It is enabled by default but you can customise HTTPS port with `https.port` config. and env. var `HTTPS_PORT`. You can create upload any certificate you want in the Otoroshi UI or using the API. Just go to `settings (cog icon) / SSL certificates`.\n\n@@@ note { title=\"Experimental Feature\" }\nDynamic SSL/TLS termination is an experimental feature. It can change until it becomess an official feature\n@@@\n\n@@@ note { title=\"TLS 1.3 support\" }\nOtoroshi does support TLS 1.3 when used in combination with JDK 11\n\n\n@@@\n\n@@@ div { .centered-img }\n\n@@@\n\nHere you can add your own certificates, your own CA and even create self signed certificates or certificates from CAs. You can enable auto renewal of thoses self signed certificates or certificates generated. Certificates have to be created with the certificate chain and the private key in PEM format with no password on the private key.\n\nYou can remove the password of a key with the following command\n\n```sh\nopenssl rsa -in keywithpassword.key -out keywithoutpassword.key\n```\n\n@@@ div { .centered-img }\n\n@@@\n\n"},{"name":"1-groups.md","id":"/usage/1-groups.md","url":"/usage/1-groups.html","title":"Managing service groups","content":"# Managing service groups\n\nGo to `settings (cog icon) / All service groups` to access the list of service groups.\n\n@@@ div { .centered-img }\n\n@@@\n\nAnd you should see the list of existing `Service groups`.\n\n@@@ div { .centered-img }\n\n@@@\n\nBut what is a `Service group` anyway ?\n\n## Otoroshi entities\n\nThere are 3 major entities at the core of Otoroshi :\n\n* **service groups**\n* service descriptors\n* api keys\n\n@@@ div { .centered-img }\n\n@@@\n\nA `service group` is just some kind of logical container for `service descriptors`. A `service group` also has some `api keys` assigned that will be used to access all the `service descriptors` contained in the `service group`.\n\n## Create a service group\n\nA `service group` is a really simple structure with an `id`, a name and a description. To create a new one, just click on the `Add item` button.\n\n@@@ div { .centered-img }\n\n@@@\n\nmodify the name and the description of the group\n\n@@@ div { .centered-img }\n\n@@@\n\nand click on `Create group`\n\n@@@ div { .centered-img }\n\n@@@\n\nThen, you should find your brand new `Service group` in the list of `Service groups`\n\n@@@ div { .centered-img }\n\n@@@\n\n## Update a service\n\nTo update a `Service group`, just click on the edit button of your `Service group`\n\n@@@ div { .centered-img }\n\n@@@\n\nUpdate the name and description of the `Service group` and click on the `Update group` button to validate name update.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Delete a service group\n\nTo delete a `Service group`, just click on the delete button of your `Service group`\n\n@@@ div { .centered-img }\n\n@@@\n\nFinally confirm the command\n\n@@@ div { .centered-img }\n\n@@@\n"},{"name":"2-services.md","id":"/usage/2-services.md","url":"/usage/2-services.html","title":"Managing services","content":"# Managing services\n\nNow let's create services. Services or `service descriptor` let you declare how to proxy a call from a domain name to another domain name (or multiple domain names). Let's say you have an API exposed on `http://192.168.0.42` and I want to expose it on `https://my.api.foo`. Otoroshi will proxy all calls to `https://my.api.foo` and forward them to `http://192.168.0.42`. While doing that, it will also log everyhting, control accesses, etc.\n\n## Otoroshi entities\n\nThere are 3 major entities at the core of Otoroshi\n\n* service groups\n* **service descriptors**\n* api keys\n\n@@@ div { .centered-img }\n\n@@@\n\nA `service descriptor` is contained in a `service group` and is allowed to be accessed by all the `api key`s authorized on the `service group`.\n\n## Create a service descriptor\n\nTo create a `service descriptor`, click on `Add service` on the Otoroshi sidebar. Then you will be asked to choose a name for the service what is the group of the service. You also have two buttons to create a new group and assign it to the service and create a new group with a name based on the service name.\n\nYou will have a serie of toggle buttons to\n\n* activate / deactivate a service\n* display maintenance page for a service\n* display contruction page for a service\n* enable otoroshi custom response headers containing request id, latency, etc \n* force https usage on the exposed service\n\nThen, you will be able to choose the URL that will be used to reach your new service on Otoroshi.\n\n@@@ div { .centered-img #service-flags }\n\n@@@\n\nIn the `service targets` section, you will be able to choose where the call will be forwarded. You can use multiple targets, in that case, Otoroshi will perform a round robin load balancing between the targets. If the `override Host header` toggle is one, the host header will be changed for the host of the target. For example, if you request `http://www.oto.tools/api` with a target to `http://www-internal.service.local/api`, the target will receive a `Host: www-internal.service.local` instead of `Host: www.oto.tools`.\n\nYou can also specify a target root, if you say that the target root is `/foo/`, then any call to `https://my.api.foo` will call `http://192.168.0.42/foo/` and nay call to `https://my.api.foo/bar` will call `http://192.168.0.42/foo/bar`.\n\nIn the URL patterns section, you will be able to choose, URL by URL which is private and which is public. By default, all services are private and each call must provide an `api key`. But sometimes, you need to access a service publicly. In that case, you can provide patterns (regex) to make some or all URL public (for example with the pattern `/.*`). You also have a `private pattern` field to restrict public patterns.\n\n@@@ div { .centered-img #targets }\n\n@@@\n\n### Otoroshi exchange protocol\n\nIf you enable secure communication for a given service, you will have to add a filter on the target application that will take the `Otoroshi-State` header and return it in a header named `Otoroshi-State-Resp`. Otoroshi is also sending a `JWT token`in a header named `Otoroshi-Claim` that the target app can validate.\n\n@@@ div { .centered-img }\n\n@@@\n\nThe `Otoroshi-Claim` is a JWT token containing some informations about the service that is called and the client if available. \n\nBy default, the otoroshi jwt token is signed with the `app.claim.sharedKey` config property (or using the `$CLAIM_SHAREDKEY` env. variable) and uses the `HMAC512` signing algorythm. But it is possible to customize how the token is signed from the service descriptor page in the `Otoroshi exchange protocol` section. \n\n@@@ div { .centered-img }\n\n@@@\n\nusing another signing algo.\n\n@@@ div { .centered-img }\n\n@@@\n\nhere you can choose the signing algorithm and the secret/keys used. You can use syntax like `${env.MY_ENV_VAR}` or `${config.my.config.path}` to provide secret/keys values. \n\nFor example, for a service named `my-service` with a signing key `secret` with `HMAC512` signing algorythm, the basic JWT token that will be sent should look like the following\n\n```\neyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiItLSIsImF1ZCI6Im15LXNlcnZpY2UiLCJpc3MiOiJPdG9yb3NoaSIsImV4cCI6MTUyMTQ0OTkwNiwiaWF0IjoxNTIxNDQ5ODc2LCJqdGkiOiI3MTAyNWNjMTktMmFjNy00Yjk3LTljYzctMWM0ODEzYmM1OTI0In0.mRcfuFVFPLUV1FWHyL6rLHIJIu0KEpBkKQCk5xh-_cBt9cb6uD6enynDU0H1X2VpW5-bFxWCy4U4V78CbAQv4g\n```\n\nif you decode it, the payload will look something like\n\n```json\n{\n \"sub\": \"apikey_client_id\",\n \"aud\": \"my-service\",\n \"iss\": \"Otoroshi\",\n \"exp\": 1521449906,\n \"iat\": 1521449876,\n \"jti\": \"71025cc19-2ac7-4b97-9cc7-1c4813bc5924\"\n}\n```\n\nIf you want to validate the `Otoroshi-Claim` on the target app side to ensure that the input requests only comes from `Otoroshi`, you will have to write an HTTP filter to do the job. For instance, if you want to write a filter to make sure that requests only comes from Otoroshi, you can write something like the following (using playframework 2.6).\n\nScala\n: @@snip [filter.scala](../snippets/filter.scala)\n\nJava\n: @@snip [filter.java](../snippets/filter.java)\n\n\n### Canary mode\n\nOtoroshi provides a feature called `Canary mode`. It lets you define new targets for a service, and route a percentage of the traffic on those targets. It's a good way to test a new version of a service before public release. As any client need to be routed to the same version of targets any time, Otoroshi will issue a special header and a cookie containing a `session id`. The header is named `Otoroshi-Canary-Id`.\n\n@@@ div { .centered-img }\n\n@@@\n\n### Service health check\n\nOtoroshi is also capable of checking the health of a service. You can define a URL that will be tested, and Otoroshi will ping that URL regularly. Will doing so, Otoroshi will pass a numeric value in a header named `Otoroshi-Health-Check-Logic-Test`. You can respond with a header named `Otoroshi-Health-Check-Logic-Test-Result` that contains the value of `Otoroshi-Health-Check-Logic-Test` + 42 to indicate that the service is working properly.\n\n@@@ div { .centered-img }\n\n@@@\n\n### Service circuit breaker\n\nIn Otoroshi, each service has its own client settings with a circuit breaker and some retry capabilities. In the `Client settings` section, you will be able to customize the client's behavior.\n\n@@@ div { .centered-img }\n\n@@@\n\n### Service settings\n\nYou can also provide some additionnal information about a given service, like an `Open API` descriptor, some metadata, a list of whitelisted/blacklisted ip addresses, etc.\n\nHere you can also define some headers that will be added to each request to the targets. And you will be able to define headers to route the call only if the defined header is present on the request.\n\n@@@ div { .centered-img #service-meta }\n\n@@@\n\n### Read only\n\nThe read only flag on a service descriptor means that this service can only be used with `HEAD`, `OPTIONS` and `GET` http verbs. You can also active the same flag on `ApiKey`s to be more specific on who cannot use write http verbs.\n\n### CORS \n\nIf you enabled this section, CORS will be automatically supported on the current service provider. The pre-flight request will be handled by Otoroshi. You can customize every CORS headers :\n\n@@@ div { .centered-img }\n\n@@@\n\n### Service authentication\n\nSee @ref:[Aauthentication](./9-auth.md)\n\n### Custom error templates\n\nFinally, you can define custom error templates that will be displayed when an error occurs when Otoroshi try to reach the target or when Otoroshi itself has an error. You can also define custom templates for maintenance and service pages.\n"},{"name":"3-apikeys.md","id":"/usage/3-apikeys.md","url":"/usage/3-apikeys.html","title":"Managing API keys","content":"# Managing API keys\n\nNow that you know how to create service groups and service descriptors, we will see how to create API keys.\n\n## Otoroshi entities\n\nThere are 3 major entities at the core of Otoroshi.\n\n* service groups\n* service descriptors\n* **api keys**\n\n@@@ div { .centered-img }\n\n@@@\n\nAn `API key` related to a `service group` to allow you to access any `service descriptor` contained in a `service group`. You can, of course, create multiple `API key` for a given `service group`.\n\nIn the Otoroshi admin dashboard, we chose to access `API keys` from `service descriptors` only, but when you access `API keys` for a `service descriptor`, you actually access `API keys` for the `service group` containing the `service descriptor`.\n\n`API keys` can be provided to Otoroshi through :\n\n* `Otoroshi-Authorization: Basic $base64(client_id:client_secret)` header, in that case, the `Otoroshi-Authorization` header will **not** be sent to the target. `Basic ` is optional.\n* `Authorization: Basic $base64(client_id:client_secret)` header, in that case, the `Authorization` header **will** be sent to the target\n* `Otoroshi-Token: Bearer $jwt_token` where the JWT token has been signed with the `API key` client secret, in that case, the `Otoroshi-Token` header will **not** be sent to the target. `Bearer ` is optional.\n* `Authorization: Bearer $jwt_token` where the JWT token has been signed with the `API key` client secret, in that case, the `Authorization` header **will** be sent to the target\n* `Cookie: access_token=$jwt_token;` where the JWT token has been signed with the `API key` client secret, in that case, the cookie named `access_token` **will** be sent to the target\n* `Otoroshi-Client-Id` and `Otoroshi-Client-Secret` headers, in that case the `Otoroshi-Client-Id` and `Otoroshi-Client-Secret` headers will not be sent to the target.\n\n## List API keys for a service descriptor\n\nGo to a service descriptor using `All services` quick link in the sidebar or the search box.\n\n@@@ div { .centered-img }\n\n@@@\n\nSelect a `service descriptor`.\n\n@@@ div { .centered-img }\n\n@@@\n\nClick on `API keys` in the sidebar\n\n@@@ div { .centered-img }\n\n@@@\n\nYou should see the list of API keys for that `service descriptor`\n\n@@@ div { .centered-img }\n\n@@@\n\n## Create an API key for a service descriptor\n\n@@@ div { .centered-img }\n\n@@@\n\nYou can add a name for your new API key, you can also change client's id and client's secret. You can also configure the throttling rate of the API key (calls per second), and the authorized number of call per day and per month. You may also activate or de-activate the api key from that screen.\n\nInformations about current quotas usage will be returned in response headers.\n\n* `Otoroshi-Daily-Calls-Remaining` : authorized calls remaining for this day\n* `Otoroshi-Monthly-Calls-Remaining` : authorized calls remaining for this month\n* `Otoroshi-Proxy-Latency` : latency induced by Otoroshi\n* `Otoroshi-Upstream-Latency` : latency between Otoroshi and target\n\n@@@ div { .centered-img #quotas }\n\n@@@\n\n@@@ warning\nDaily and monthly quotas are based on the following rules :\n\n* daily quota is computed between 00h00:00.000 and 23h59:59.999\n* monthly qutoas is computed between the first day of the month at 00h00:00.000 and the last day of the month at 23h59:59.999\n@@@\n\n## Update an API key\n\nTo update an `API key`, just click on the edit button of your `API key`\n\n@@@ div { .centered-img }\n\n@@@\n\nUpdate the name, secret, state and quotas (if needed) of the `API key` and click on the `Update API key` button\n\n@@@ div { .centered-img }\n\n@@@\n\n## Delete an API key\n\nTo delete an `API key`, just click on the delete button of your `API key`\n\n@@@ div { .centered-img }\n\n@@@\n\nand confirm the command\n\n@@@ div { .centered-img }\n\n@@@\n\n### Read only\n\nThe read only flag on an `ApiKey` this apikey can only use allowed services with `HEAD`, `OPTIONS` and `GET` http verbs.\n\n## Use a JWT token to pass an API key\n\nYou can use a JWT token to pass an API key to Otoroshi. \nYou can use `Otoroshi-Authorization: Bearer $jwt_token`, `Authorization: Bearer $jwt_token` header and `Cookie: access_token=$jwt_token;` to pass the JWT token.\nYou have to create a JWT token with a signing algorythm that can be `HS256` or `HS512`. Then you have to provide an `iss` claim with the value of your API key `clientId` and sign the JWT token with your API key `clientSecret`.\n\nFor example, with an API key like `clientId=abcdef` and `clientSecret=1234456789`, your JWT token should look like\n\n```json\n{\n \"alg\": \"HS256\",\n \"typ\": \"JWT\"\n}\n{\n \"iss\":\"abcdef\",\n \"name\": \"John Doe\",\n \"admin\": true\n}\n```\n\nin that case, when you sign the token with the secret of the API key `1234456789`, the signature will be `_eancnYCD3makSSox2v2xErjNYkRtcX558QiJGCbino`, resulting in a encoded JWT header like\n\n```\neyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.\neyJpc3MiOiJhYmNkZWYiLCJuYW1lIjoiSm9obiBEb2UiLCJhZG1pbiI6dHJ1ZX0.\n_eancnYCD3makSSox2v2xErjNYkRtcX558QiJGCbino\n```\n"},{"name":"4-monitor.md","id":"/usage/4-monitor.md","url":"/usage/4-monitor.html","title":"Monitoring services","content":"# Monitoring services\n\nOnce you have declared services, you can monitor them with Otoroshi.\n\n@@@ warning\nYou have to use [Elastic](https://www.elastic.co) to enable analytics features in Otoroshi\n@@@\n\nOnce you have setup @ref:[Otoroshi events push to an elastic cluster](../integrations/analytics.md) (through webhooks, kafka, or elastic integration) you can setup Otoroshi events read from an elastic cluster. Go to `settings (cog icon) / Danger Zone` and expand the `Analytics: Elastic dashboard datasource (read)` section.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Service healthcheck\n\nIf you have defined an health check URL in the service descriptor, you can access the health check page from the sidebar of the service page.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Service live stats\n\nYou can also monitor live stats like total of served request, average response time, average overhead, etc. The live stats page can be accessed from the sidebar of the service page.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Service analytics\n\nYou can also get some aggregated metrics. The analytics page can be accessed from the sidebar of the service page.\n\n@@@ div { .centered-img }\n\n@@@\n"},{"name":"5-sessions.md","id":"/usage/5-sessions.md","url":"/usage/5-sessions.html","title":"Managing sessions","content":"# Managing sessions\n\nWith Otoroshi you can manage sessions of connected users and you can discard sessions whenever you want. Session last 24h by default and you can customize them with `app.backoffice.session.exp` and `app.privateapps.session.exp` @ref:[config keys](../firstrun/configfile.md)\n\n## Admin. sessions\n\nTo see last current admin session on Otoroshi from the UI, go to `settings (cog icon) / Admins sessions`. Here you can discard individual sessions or all sessions at once using `Discard session` and `Discard all sessions` buttons.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Private apps. session\n\nTo see last current admin session on Otoroshi from the UI, go to `settings (cog icon) / Priv. apps sessions`. Here you can discard individual sessions or all sessions at once using `Discard session` and `Discard all sessions` buttons.\n\n@@@ div { .centered-img }\n\n@@@\n"},{"name":"6-audit.md","id":"/usage/6-audit.md","url":"/usage/6-audit.html","title":"Auditing Otoroshi","content":"# Auditing Otoroshi\n\nWith Otoroshi, any admin action and any sucpicious/alert action is recorded. These records are stored in Otoroshi's datastore (only the last n records, defined by the `app.events.maxSize` @ref:[config key](../firstrun/configfile.md)). All the records can be send through the analytics mechanism (WebHook, Kafka, Elastic) for external and/or further usage. We recommand sending away those records for security reasons.\n\n@@@ warning\nYou have to use [Elastic](https://www.elastic.co) to enable analytics features in Otoroshi. See @ref:[Elastic setup section](../integrations/analytics.md)\n@@@\n\n## Audit trail\n\nTo see last `app.events.maxSize` admin actions on Otoroshi from the UI, go to `settings (cog icon) / Audit log`.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Alerts\n\nTo see last `app.events.maxSize` alerts on Otoroshi from the UI, go to `settings (cog icon) / Alerts log`.\n\n@@@ div { .centered-img }\n\n@@@\n\n## List of possible alerts\n\n```\nMaxConcurrentRequestReachedAlert\nCircuitBreakerOpenedAlert\nCircuitBreakerClosedAlert\nSessionDiscardedAlert\nSessionsDiscardedAlert\nPanicModeAlert\nOtoroshiExportAlert\nU2FAdminDeletedAlert\nBlackListedBackOfficeUserAlert\nAdminLoggedInAlert\nAdminFirstLogin\nAdminLoggedOutAlert\nDbResetAlert\nDangerZoneAccessAlert\nGlobalConfigModification\nRevokedApiKeyUsageAlert\nServiceGroupCreatedAlert\nServiceGroupUpdatedAlert\nServiceGroupDeletedAlert\nServiceCreatedAlert\nServiceUpdatedAlert\nServiceDeletedAlert\nApiKeyCreatedAlert\nApiKeyUpdatedAlert\nApiKeyDeletedAlert\n```\n"},{"name":"7-metrics.md","id":"/usage/7-metrics.md","url":"/usage/7-metrics.html","title":"Otoroshi global metrics","content":"# Otoroshi global metrics\n\nOtoroshi provide some global metrics about services usage. Go to `settings (cog icon) / Global Ananlytics`\n\n@@@ warning\nYou have to use [Elastic](https://www.elastic.co) to enable analytics features in Otoroshi. See @ref:[Elastic setup section](../integrations/analytics.md)\n@@@\n\n@@@ div { .centered-img }\n\n@@@\n"},{"name":"8-importsexports.md","id":"/usage/8-importsexports.md","url":"/usage/8-importsexports.html","title":"Import and export","content":"# Import and export\n\nWith Otoroshi you can easily save the current state of the proxy and restore it later. Go to `settings (cog icon) / Danger Zone` and scroll to the bottom of the page\n\n## Full export\n\nClick on the `Full export` button.\n\n@@@ div { .centered-img }\n\n@@@\n\nYour browser will start to download a JSON file containing the internal state of your Otoroshi cluster.\n\n@@@ div { .centered-img }\n\n@@@\n\n## Full import\n\nIf you want to restore an export, go to `settings (cog icon) / Danger Zone` and scroll to the bottom of the page. Click on the `Recover from full export file` button\n\n@@@ div { .centered-img }\n\n@@@\n\nChoose export file on your system.\n\n@@@ div { .centered-img }\n\n@@@\n\nClick on the `Flush datastore and import ...` button, confirm and you will be logged out.\n\n@@@ div { .centered-img }\n\n@@@\n"},{"name":"9-auth.md","id":"/usage/9-auth.md","url":"/usage/9-auth.html","title":"Authentication","content":"# Authentication\n\nYou can create auth. configuration in Otoroshi. Just go to `settings (cog icon) / Global auth. configs`.\n\n## OAuth 2\n\nCreate a new `Generic oauth2 provider` config and customize the following informations:\n\n```json\n{\n \"clientId\": \"xxxx\",\n \"clientSecret\": \"xxxx\",\n \"authorizeUrl\": \"http://yourOAuthServer/oauth/authorize\",\n \"tokenUrl\": \"http://yourOAuthServer/oauth/token\",\n \"userInfoUrl\": \"http://yourOAuthServer/userinfo\",\n \"loginUrl\": \"http://yourOAuthServer/login\",\n \"logoutUrl\": \"http://yourOAuthServer/logout?redirectQueryParamName=${redirect}\",\n \"accessTokenField\": \"access_token\",\n \"nameField\": \"name\",\n \"emailField\": \"email\",\n \"callbackUrl\": \"http://privateapps.oto.tools/privateapps/generic/callback\"\n}\n```\n\nIf used for BackOffice authentication, the callback url should be `http://otoroshi.oto.tools/backoffice/auth0/callback`.\n\nFor `logoutUrl`, `redirectQueryParamName` is a parameter with a name specific to your OAuth2 provider (for example, in Auth0, this parameter is called `returnTo`, in Kecloak it is called `redirect_uri`).\n\nif you are using a [KeyCloak](https://www.keycloak.org/) server, you can configure it this way, assuming you are using the master realm and you created a new client with a client secret, callback urls set to `http://privateapps.oto.tools/*`.\n\n```json\n{\n \"clientId\": \"clientId\",\n \"clientSecret\": \"clientSecret\",\n \"authorizeUrl\": \"http://keycloakHost/auth/realms/master/protocol/openid-connect/auth\",\n \"tokenUrl\": \"http://keycloakHost/auth/realms/master/protocol/openid-connect/token\",\n \"userInfoUrl\": \"http://keycloakHost/auth/realms/master/protocol/openid-connect/userinfo\",\n \"loginUrl\": \"http://keycloakHost/auth/realms/master/protocol/openid-connect/auth\",\n \"logoutUrl\": \"http://keycloakHost/auth/realms/master/protocol/openid-connect/logout?redirect_uri=${redirect}\",\n \"accessTokenField\": \"access_token\",\n \"nameField\": \"name\",\n \"emailField\": \"email\",\n \"callbackUrl\": \"http://privateapps.oto.tools/privateapps/generic/callback\"\n}\n```\n\n## Ldap\n\nCreate a new `Ldap auth. provider` config and customize the following informations:\n\n```json\n{\n \"serverUrl\": \"ldap://ldap.forumsys.com:389\",\n \"searchBase\": \"dc=example,dc=com\",\n \"groupFilter\": \"ou=chemists\",\n \"searchFilter\": \"(mail=${username})\",\n \"adminUsername\": \"cn=read-only-admin,dc=example,dc=com\",\n \"adminPassword\": \"password\",\n \"nameField\": \"cn\",\n \"emailField\": \"mail\"\n}\n```\n\n## In Memory\n\nCreate a new `In memory auth. provider` config and then you will be able to create new users. To set the password, just click on the `Set password` button. It will generate a BCrypt hash of the password you typed.\n\n## Auth0\n\nCreate a new OAuth 2 config and add the following informations:\n\n```json\n{\n \"clientId\": \"yourAuth0ClientId\",\n \"clientSecret\": \"yourAuth0ClientSecret\",\n \"authorizeUrl\": \"https://yourAuth0Domain/authorize\",\n \"tokenUrl\": \"https://yourAuth0Domain/oauth/token\",\n \"userInfoUrl\": \"https://yourAuth0Domain/userinfo\",\n \"loginUrl\": \"https://yourAuth0Domain/authorize\",\n \"logoutUrl\": \"https://yourAuth0Domain/v2/logout?returnTo=${redirect}\",\n \"accessTokenField\": \"access_token\",\n \"nameField\": \"name\",\n \"emailField\": \"email\",\n \"otoroshiDataField\": \"app_metadata | otoroshi_data\",\n \"callbackUrl\": \"http://privateapps.oto.tools/privateapps/generic/callback\"\n}\n```\n\nIf you enable Otoroshi exchange protocol, the JWT xill have the following fields (all optional)\n\n* `email`\n* `name`\n* `picture`\n* `user_id`\n* `given_name`\n* `family_name`\n* `gender`\n* `locale`\n* `nickname`\n\nIn Auth0, the metadata is a flat object placed in the `profile / http://yourdomain/app_metadata / otoroshi_data`. You might need to write an Auth0 rule to copy app metadata under `http://yourdomain/app_metadata`, the `http://yourdomain/app_metadata` value is a config property `app.appMeta`. The rule could be something like the following\n\n```js\nfunction (user, context, callback) {\n var namespace = 'http://yourdomain/';\n context.idToken[namespace + 'user_id'] = user.user_id;\n context.idToken[namespace + 'user_metadata'] = user.user_metadata;\n context.idToken[namespace + 'app_metadata'] = user.app_metadata;\n callback(null, user, context);\n}\n```"},{"name":"index.md","id":"/usage/index.md","url":"/usage/index.html","title":"Using Otoroshi","content":"# Using Otoroshi\n\nNow we will see how to use Otoroshi for basic tasks that will be useful for your day to day work with Otoroshi.\n\n@@@ index\n\n* [create group](./1-groups.md)\n* [create service](./2-services.md)\n* [create API Keys](./3-apikeys.md)\n* [monitor service](./4-monitor.md)\n* [sessions management](./5-sessions.md)\n* [Audit trail and alerts](./6-audit.md)\n* [Global metrics](./7-metrics.md)\n* [Exports and imports](./8-importsexports.md)\n* [Authentication](./9-auth.md)\n\n@@@\n"},{"name":"videos.md","id":"/videos.md","url":"/videos.html","title":"Video tutorials","content":"# Video tutorials\n\n## Create a service restricted by an api key\n\nhere you will learn how to create a simple service that will proxy an existing api and how to secure it using an api key.\n\n\n\n## Use CLI to create load balanced services with retry\n\nhere you will learn how to create a service using the Otoroshi CLI and how to use load balancing with retry features of Otoroshi.\n\n"}] \ No newline at end of file