Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion endpoint_route_handler/models/ir_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,17 @@ def _generate_routing_rules(self, modules, converters):
self._endpoint_routing_rules(),
)

@classmethod
def _endpoint_routing_rules_kwargs(cls):
return {}

@classmethod
def _endpoint_routing_rules(cls):
"""Yield custom endpoint rules"""
e_registry = cls._endpoint_route_registry(http.request.env)
for endpoint_rule in e_registry.get_rules():
for endpoint_rule in e_registry.get_rules(
**cls._endpoint_routing_rules_kwargs()
):
_logger.debug("LOADING %s", endpoint_rule)
endpoint = endpoint_rule.endpoint
for url in endpoint_rule.routing["routes"]:
Expand Down
194 changes: 194 additions & 0 deletions endpoint_route_handler_filter/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
.. image:: https://odoo-community.org/readme-banner-image
:target: https://odoo-community.org/get-involved?utm_source=readme
:alt: Odoo Community Association

=============================
Endpoint Route Handler Filter
=============================

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:ca628323cca8616be9a95ead4ca25834b144f54fdeeb0277d7d453db02c914bc
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fweb--api-lightgray.png?logo=github
:target: https://github.com/OCA/web-api/tree/19.0/endpoint_route_handler_filter
:alt: OCA/web-api
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/web-api-19-0/web-api-19-0-endpoint_route_handler_filter
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/web-api&target_branch=19.0
:alt: Try me on Runboat

|badge1| |badge2| |badge3| |badge4| |badge5|

This module allows FastAPI-based endpoints (built on top of ``fastapi``
/ ``OCA/rest-framework``) to be selectively registered depending on the
Odoo **process** that is running, instead of always being exposed on
every worker regardless of which port it listens on.

This makes it possible to run two (or more) Odoo processes against the
same database:

- a **public** process (e.g. port 8069) that never registers the
internal endpoints, and
- an **internal** process (e.g. port 8070), reachable only from a
trusted network (VPN, internal VLAN, firewall rule), that does
register them.

The module does not implement any authentication, authorization, or
network restriction itself — that is left to the FastAPI endpoint's own
auth mechanism (API key, JWT, etc.) and to your reverse proxy / firewall
configuration. It only controls **route existence** per process.

**Table of contents**

.. contents::
:local:

Use Cases / Context
===================

Odoo serves all registered controllers — standard web controllers as
well as any FastAPI endpoints mounted via ``fastapi.endpoint`` — through
a single WSGI application, regardless of which port (``http_port`` /
longpolling port) a given worker process is listening on. There is no
built-in mechanism to say "these routes exist only on port X."

In practice this means that once a FastAPI endpoint is installed, it is
reachable from every process and every port Odoo happens to be running
on, including the main public-facing one. Restricting access to it then
falls entirely on network-level controls (reverse proxy IP allowlists,
firewall rules), which:

- protect the network path to the endpoint, but not the fact that the
route exists on every process, and
- offer no protection against a request that legitimately originates
from an allowed network (e.g. a compromised internal host).

For internal, technical, or automation-only endpoints — where the goal
is to expose a narrow set of actions to internal systems without
enlarging the attack surface of the main public Odoo instance — it is
preferable that the routes simply do not exist on the public process at
all.

This module addresses that by making route registration conditional on a
process-local setting (not a database value, since
``ir.config_parameter`` is shared across all processes and workers
connected to the same database and would not allow differentiating
between them). This allows a deployment pattern of two Odoo processes,
same codebase and database, started with different configuration:

- one process without the flag → internal routes never registered
(public, e.g. port 8069),
- one process with the flag → internal routes registered (internal only,
e.g. port 8070, restricted at the network layer to trusted sources).

Network-level restriction (firewall/reverse proxy) and endpoint-level
authentication (JWT/API key on the FastAPI app) remain necessary and are
complementary to this module, not replaced by it. This module removes
one layer of exposure (route existence on the public process); it does
not replace access control on the internal one.

Configuration
=============

The module reads a single process-local setting to decide whether to
register the internal endpoint routes. It does **not** read this setting
from ``ir.config_parameter``, since that table is shared by every
process and worker connected to the same database and would not allow
two processes to behave differently.

Choose **one** of the two mechanisms below, matching what the code
implements.

Option A — Environment variable
-------------------------------

Set ODOO_ENDPOINT_ROUTE_HANDLER_FILTER_GROUP for setting the allowed
route groups.

Set ODOO_ENDPOINT_ROUTE_HANDLER_FILTER_IGNORE to ignore some route
groups.

Option B — Config file key
--------------------------

Add ``odoo_endpoint_route_handler_ignore`` and
``odoo_endpoint_route_handler_filter`` under ``[options]`` in the config
file used to start the internal process only. Odoo's config loader
accepts undeclared keys from the config file (it just won't accept them
as CLI flags), so no changes to Odoo core are required.

.. code:: ini

; odoo_public.conf
[options]
http_port = 8069
odoo_endpoint_route_handler_ignore = internal,my_other_route ; internal and my_other_route are not accepted


; odoo_internal.conf
[options]
http_port = 8070
odoo_endpoint_route_handler_filter = internal,my_other_route ; Only internal and my_other_route are accepted

Notes
-----

- If you run the internal process with ``--workers=N``, all N worker
processes inherit the same setting — this is expected and desired.
- The setting only affects whether the routes are added to the routing
map. It does not grant or restrict any permission by itself.

Bug Tracker
===========

Bugs are tracked on `GitHub Issues <https://github.com/OCA/web-api/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/web-api/issues/new?body=module:%20endpoint_route_handler_filter%0Aversion:%2019.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.

Do not contact contributors directly about support or help with technical issues.

Credits
=======

Authors
-------

* Dixmit

Contributors
------------

- `Dixmit <https://dixmit.com>`__

- Enric Tobella

Maintainers
-----------

This module is maintained by the OCA.

.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org

OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.

This module is part of the `OCA/web-api <https://github.com/OCA/web-api/tree/19.0/endpoint_route_handler_filter>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
1 change: 1 addition & 0 deletions endpoint_route_handler_filter/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
15 changes: 15 additions & 0 deletions endpoint_route_handler_filter/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Copyright 2026 Dixmit
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

{
"name": "Endpoint Route Handler Filter",
"summary": """This Addon allows us to filter the route handler
depending on the instance. This way we can have dedicated runners.""",
"version": "19.0.1.0.0",
"license": "AGPL-3",
"author": "Dixmit,Odoo Community Association (OCA)",
"website": "https://github.com/OCA/web-api",
"depends": [
"endpoint_route_handler",
],
}
1 change: 1 addition & 0 deletions endpoint_route_handler_filter/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import ir_http
40 changes: 40 additions & 0 deletions endpoint_route_handler_filter/models/ir_http.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Copyright 2021 Camptocamp SA
# @author: Simone Orsi <simone.orsi@camptocamp.com>
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

import logging
import os

from odoo import models, tools

_logger = logging.getLogger(__name__)


class IrHttp(models.AbstractModel):
_inherit = "ir.http"

@classmethod
def _endpoint_routing_rules_kwargs(cls):
result = super()._endpoint_routing_rules_kwargs()
group_filter = tools.config.get(
"odoo_endpoint_route_handler_filter"
) or os.getenv("ODOO_ENDPOINT_ROUTE_HANDLER_FILTER_GROUP")
no_group_filter = tools.config.get(
"odoo_endpoint_route_handler_ignore"
) or os.getenv("ODOO_ENDPOINT_ROUTE_HANDLER_FILTER_IGNORE")
if not group_filter and not no_group_filter:
return result
where = result.get("where")
if where:
where += " AND "
else:
where = "WHERE "
conditions = []
if group_filter:
group_filter_sql = "','".join(group_filter.split(","))
conditions.append(f"route_group in ('{group_filter_sql}')")
if no_group_filter:
no_group_filter_sql = "','".join(no_group_filter.split(","))
conditions.append(f"route_group not in ('{no_group_filter_sql}')")
result.update({"where": f"{where} {' AND '.join(conditions)}"})
return result
3 changes: 3 additions & 0 deletions endpoint_route_handler_filter/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[build-system]
requires = ["whool"]
build-backend = "whool.buildapi"
39 changes: 39 additions & 0 deletions endpoint_route_handler_filter/readme/CONFIGURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
The module reads a single process-local setting to decide whether to
register the internal endpoint routes. It does **not** read this setting
from `ir.config_parameter`, since that table is shared by every process
and worker connected to the same database and would not allow two
processes to behave differently.

Choose **one** of the two mechanisms below, matching what the code
implements.

## Option A — Environment variable

Set ODOO_ENDPOINT_ROUTE_HANDLER_FILTER_GROUP for setting the allowed route groups.

Set ODOO_ENDPOINT_ROUTE_HANDLER_FILTER_IGNORE to ignore some route groups.

## Option B — Config file key

Add `odoo_endpoint_route_handler_ignore` and `odoo_endpoint_route_handler_filter` under `[options]` in the config file used to start the internal process only.
Odoo's config loader accepts undeclared keys from the config file (it just won't accept them as CLI flags), so no changes to Odoo core are required.

```ini
; odoo_public.conf
[options]
http_port = 8069
odoo_endpoint_route_handler_ignore = internal,my_other_route ; internal and my_other_route are not accepted


; odoo_internal.conf
[options]
http_port = 8070
odoo_endpoint_route_handler_filter = internal,my_other_route ; Only internal and my_other_route are accepted
```

## Notes

- If you run the internal process with `--workers=N`, all N worker
processes inherit the same setting — this is expected and desired.
- The setting only affects whether the routes are added to the routing
map. It does not grant or restrict any permission by itself.
41 changes: 41 additions & 0 deletions endpoint_route_handler_filter/readme/CONTEXT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
Odoo serves all registered controllers — standard web controllers as
well as any FastAPI endpoints mounted via `fastapi.endpoint` — through a
single WSGI application, regardless of which port (`http_port` /
longpolling port) a given worker process is listening on. There is no
built-in mechanism to say "these routes exist only on port X."

In practice this means that once a FastAPI endpoint is installed, it is
reachable from every process and every port Odoo happens to be running
on, including the main public-facing one. Restricting access to it then
falls entirely on network-level controls (reverse proxy IP allowlists,
firewall rules), which:

- protect the network path to the endpoint, but not the fact that the
route exists on every process, and
- offer no protection against a request that legitimately originates
from an allowed network (e.g. a compromised internal host).

For internal, technical, or automation-only endpoints — where the goal
is to expose a narrow set of actions to internal systems without
enlarging the attack surface of the main public Odoo instance — it is
preferable that the routes simply do not exist on the public process at
all.

This module addresses that by making route registration conditional on
a process-local setting (not a database value, since `ir.config_parameter`
is shared across all processes and workers connected to the same
database and would not allow differentiating between them). This allows
a deployment pattern of two Odoo processes, same codebase and database,
started with different configuration:

- one process without the flag → internal routes never registered
(public, e.g. port 8069),
- one process with the flag → internal routes registered (internal
only, e.g. port 8070, restricted at the network layer to trusted
sources).

Network-level restriction (firewall/reverse proxy) and endpoint-level
authentication (JWT/API key on the FastAPI app) remain necessary and are
complementary to this module, not replaced by it. This module removes
one layer of exposure (route existence on the public process); it does
not replace access control on the internal one.
2 changes: 2 additions & 0 deletions endpoint_route_handler_filter/readme/CONTRIBUTORS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- [Dixmit](https://dixmit.com)
- Enric Tobella
18 changes: 18 additions & 0 deletions endpoint_route_handler_filter/readme/DESCRIPTION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
This module allows FastAPI-based endpoints (built on top of `fastapi` /
`OCA/rest-framework`) to be selectively registered depending on the Odoo
**process** that is running, instead of always being exposed on every worker
regardless of which port it listens on.

This makes it possible to run two (or more) Odoo processes against the
same database:

- a **public** process (e.g. port 8069) that never registers the
internal endpoints, and
- an **internal** process (e.g. port 8070), reachable only from a
trusted network (VPN, internal VLAN, firewall rule), that does
register them.

The module does not implement any authentication, authorization, or
network restriction itself — that is left to the FastAPI endpoint's own
auth mechanism (API key, JWT, etc.) and to your reverse proxy / firewall
configuration. It only controls **route existence** per process.
Binary file not shown.
Loading
Loading