Proxy iDempiere-Rest Through KrakenD
Quick notes from Carlos Ruiz here about proxying idempiere-rest through krakend
Goal
Initial goal is just to protect the iDempiere installation from the public, as the idempiere-rest is too open, it can be subject to attacks, so the recommended approach for these cases is to avoid exposing the full API and use a gateway API.
Gateway APIs can help with many different things:
- Proxy, authenticate, authorize, security, audit
- Reporting, analytics
- Billing, etc
Gateway APIs
There are ready-to-use providers, like from Amazon, Google, or Microsoft
There are also open source alternatives like:
KrakenD
Because of its simplicity and open sourceness, I chose KrakenD for this test.
KrakenD is an open source project from Spain, developed in golang.
It seems well documented, and there is a very helpful slack channel, as well as easy to reach paid support.
Steps for this test
Installation in Ubuntu 20.04
The installation in a server with Ubuntu 20.04 was very straight:
# as root apt-key adv --keyserver keyserver.ubuntu.com --recv 5DE6FD698AD6FDD2 echo "deb https://repo.krakend.io/apt stable main" | tee /etc/apt/sources.list.d/krakend.list apt-get update apt-get install krakend
As of 2024-10-02 these commands installed the version 2.7.2-0.
The installer adds:
- a single 114M binary in /usr/bin/krakend (no libraries required)
- a user krakend
- a default configuration file in /etc/krakend/krakend.json
To restart the program (for example after changing krakend.json)
systemctl restart krakend
To check the status of the program:
systemctl status krakend
Initially it shows that krakend port 8080 collided with the same port used in idempiere, so, later we need to change the port in /etc/krakend/krakend.json
Local tests with docker
I also managed to do some local tests with docker, it is basically:
docker pull devopsfaith/krakend docker run -p 8085:8085 -v $PWD:/etc/krakend/ devopsfaith/krakend
Note here two things:
- The folder where you start the docker container will become the /etc/krakend in the "server", so, it can contain the configuration files. In principle it must contain the file krakend.json
- I changed the default port here from 8080 to 8085, and consequently that needs to be changed in the krakend.json configuration file
Create a krakend.json configuration file
The easiest way to start is creating the krakend.json file in the designer: https://designer.krakend.io/
Step 1
- Name = idrest
- Port = 8085 # cannot use 8080 because is used by iDempiere
- Available hosts =
- Static address resolution
- Disable sanitize = Y
- http://localhost:8080
- # NOTE: when using krakend with docker, then use the IP address of the host, localhost doesn't work in this case
Step 2
- Add an Endpoint:
- KrakenD Endpoint = /idrest/auth/tokens
- Method = POST
- Output = JSON
- Backend API calls =
- Host = select the host configured above
- Backend endpoint = /api/v1/auth/tokens
- HTTP Verb = POST
- Response Parsing Decode as = JSON
- KrakenD Endpoint = /idrest/auth/tokens
- Method = PUT
- Output = JSON
- Headers passing to backend =
- Authorization
- Content-Type
- Enable JWT Validation
- # NOTE: JWT Validation is not mandatory, enable it if you want an extra security measure
- JWK URI = http://localhost:8080/api/v1/auth/jwk
- # NOTE: for this to work you must add a System Configurator record in iDempiere as System for IDEMPIERE_REST_EXPOSE_JWK = Y
- Algorithm = HS512
- Issuer = idempiere.org
- Enable caching = Y
- Disable JWK security = Y
- Backend API calls =
- Host = select the host configured above
- Backend endpoint = /api/v1/auth/tokens
- HTTP Verb = PUT
- Response Parsing Decode as = JSON
- Logging and metrics
- Logging = Y
- Level = DEBUG
- Logging prefix = [KRAKEND]
- Message format = default
- Send logs to Stdout = Y
- Send logs to Syslog = N
- # NOTE: this is for testing, in production is the opposite, you would like logging in Syslog
- Save the configuration and use it
From here is just about adding more endpoints for every need.
This is the simplest example, there are better ways to define the krakend.json, for example to avoid repeating the JWT validation information on every endpoint
Refining the configuration
Once you understand how is the configuration managed, in the documentation you find the tips to refine it, reusing pieces, creating templates, filtering, etc.
Sample krakend.json working for idempiere-rest
Tokens endpoints (Login)
Here you can find a sample file that works for login on idempiere-rest:
{
"$schema": "https://www.krakend.io/schema/v3.json",
"version": 3,
"host": [
"http://127.0.0.1:8080"
],
"extra_config": {
"telemetry/logging": {
"level": "DEBUG",
"prefix": "[KRAKEND]",
"syslog": false,
"stdout": true,
"format": "default"
}
},
"timeout": "3000ms",
"cache_ttl": "300s",
"name": "idrest",
"port": 8085,
"endpoints": [
{
"endpoint": "/idrest/auth/tokens",
"method": "POST",
"backend": [
{
"url_pattern": "/api/v1/auth/tokens",
"method": "POST",
"disable_host_sanitize": true
}
]
},
{
"endpoint": "/idrest/auth/tokens",
"method": "PUT",
"extra_config": {
"auth/validator": {
"alg": "HS512",
"jwk_url": "http://127.0.0.1:8080/api/v1/auth/jwk",
"issuer": "idempiere.org",
"cache": true,
"disable_jwk_security": true
}
},
"backend": [
{
"url_pattern": "/api/v1/auth/tokens",
"method": "PUT",
"disable_host_sanitize": true
}
],
"input_headers": [
"Authorization",
"Content-Type"
]
}
]
}
Check the configuration
Check the configuration
krakend check --config /etc/krakend/krakend.json --debug
Run krakend
systemct restart krakend
Change on endpoint URL (idrest)
Note that the example above is configured to answer on the URL /idrest instead of /api/v1 - and is configured to answer in port 8085, so, if testing with postman for example you need to configure the endpoints as:
http://locahost:8085/idrest/auth/tokens
Because of this you would need to open the 8085 port in your firewall, not recommended, or configure KrakenD to use SSL.
But, my preferred way of doing this is:
Proxying KrakenD through nginx
As explained in Proxy iDempiere Through Nginx you can use nginx as a proxy for iDempiere, in a similar way, I used nginx to proxy krakend adding these lines to the configuration file:
location /idrest { proxy_pass http://localhost:8085/idrest; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; }
Adding more endpoints
We already have the configuration above to do the login using idempiere-rest, but obviously we want to add more endpoints to expose in our system, following are examples for this:
GET Warehouses
This is an example for the endpoint GET warehouses.
NOTE the usage of querystring_params to instruct krakend to accept the URL parameters client, role and organization.
In a similar way are configured the endpoints auth/roles and auth/organizations:
{
"endpoint": "/idrest/auth/warehouses",
"method": "GET",
"extra_config": {
"auth/validator": {
"alg": "HS512",
"jwk_url": "http://127.0.0.1:8080/api/v1/auth/jwk",
"issuer": "idempiere.org",
"cache": true,
"disable_jwk_security": true
}
},
"input_query_strings": [
"client",
"role",
"organization"
],
"backend": [
{
"url_pattern": "/api/v1/auth/warehouses",
"method": "GET",
"disable_host_sanitize": true,
"is_collection": false,
"target": ""
}
],
"input_headers": [
"Authorization",
"Content-Type"
]
}
GET windows
The endpoint to get information from windows requires the usage of different querystring_params, for example note here the configuration for $filter, $expand, $select, $orderby, $top and $skip
{
"endpoint": "/idrest/windows",
"method": "GET",
"extra_config": {
"auth/validator": {
"alg": "HS512",
"jwk_url": "http://127.0.0.1:8080/api/v1/auth/jwk",
"issuer": "idempiere.org",
"cache": true,
"disable_jwk_security": true
}
},
"input_query_strings": [
"$filter",
"$expand",
"$select",
"$orderby",
"$top",
"$skip"
],
"backend": [
{
"url_pattern": "/api/v1/windows",
"method": "GET",
"disable_host_sanitize": true,
"is_collection": false,
"target": ""
}
],
"input_headers": [
"Authorization",
"Content-Type"
]
}
GET model (restricted to some tables)
Here is an example for the GET model configuration, note here additionally the usage of parameters (table) as part of the URL, and the ability to validate those parameters in check_expr.
In this case the URLs /idrest/models/m_product and /idrest/models/ad_user are allowed, but the attempt to read any other table will throw an error.
{
"endpoint": "/idrest/models/{table}",
"method": "GET",
"extra_config": {
"auth/validator": {
"alg": "HS512",
"jwk_url": "http://127.0.0.1:8080/api/v1/auth/jwk",
"issuer": "idempiere.org",
"cache": true,
"disable_jwk_security": true
},
"validation/cel": [
{
"check_expr": "req_params.Table.matches('m_product') || req_params.Table.matches('ad_user')"
}
]
},
"input_query_strings": [
"$filter",
"$expand",
"$select",
"$orderby",
"$top",
"$skip"
],
"backend": [
{
"url_pattern": "/api/v1/models/{table}",
"method": "GET",
"disable_host_sanitize": true,
"is_collection": false,
"target": ""
}
],
"input_headers": [
"Authorization",
"Content-Type"
]
}
POST processes (running)
Here is an example that allows to run JUST the process rv_bpartneropen, of course, more processes can be allowed adding more conditions to the check_expr:
{
"endpoint": "/idrest/processes/{process}",
"method": "POST",
"extra_config": {
"auth/validator": {
"alg": "HS512",
"jwk_url": "http://127.0.0.1:8080/api/v1/auth/jwk",
"issuer": "idempiere.org",
"cache": true,
"disable_jwk_security": true
},
"validation/cel": [
{
"check_expr": "req_params.Process.matches('rv_bpartneropen')"
}
]
},
"backend": [
{
"url_pattern": "/api/v1/processes/{process}",
"method": "POST",
"disable_host_sanitize": true,
"is_collection": false,
"target": ""
}
],
"input_headers": [
"Authorization",
"Content-Type"
]
}