Internal API Access¶
Overview¶
Certain platform administration operations (for example resetting a user’s OTP)
are exposed by the API under the /api/internal prefix. These endpoints are
privileged: they are intended to be called by trusted automation systems
(operations scripts, orchestrators, internal tools), and not by browsers or
end-user clients using the portal.
The need addressed by this feature is therefore: granting access to
/api/internal without exposing it on the usual public surface (the
portal or the standard API host), and protecting that access with strong
authentication independent of normal user authentication.
To achieve this, /api/internal is published on a dedicated hostname
(API_INTERNAL_URL), distinct from the portal and the standard API host, and
can be protected by three complementary layers:
an IP allowlist (network filtering);
dedicated mTLS (mutual TLS authentication with its own PKI, separate from the standard internal PKI);
an HMAC key at the application layer, verified by the API itself.
None of these layers is individually mandatory, but they stack: for example, you can enable only IP filtering, or add mTLS for strong transport-layer authentication.
How It Works¶
When the feature is enabled, a dedicated Traefik route is created:
Host(`API_INTERNAL_URL`) && PathPrefix(`/api/internal`)
This route is attached:
to the portal service when the deployment type is
infra_manager(the portal already relays/api/*to the API);to the api service when roles are split (
api_manager+portal_manager); the route is then opened only on theapi_managerside.
The API_INTERNAL_URL hostname acts as an isolated entry point: it is the
one that carries the dedicated mTLS authentication. This hostname-based
isolation is required because the client PKI is selected during TLS negotiation
(based on the hostname), before the /api/internal path is known. It is
therefore not possible to assign a different PKI to a simple path on a hostname
that is already in use.
Configuration Variables¶
API_INTERNAL_ENABLEDEnables the feature. Boolean value (
true/false). Default:false.API_INTERNAL_URLDedicated hostname on which
/api/internalis published (for exampleapiinternal.example.com). Required as soon as the route must be opened. Must be different from the portal hostname (PORTAL_URL) and the standard reemo_api host.API_INTERNAL_RESTRICT_IPList of IP addresses or ranges allowed to reach
/api/internal, in comma-separated CIDR format (for example"10.0.0.0/8,192.168.10.20/32"). A request coming from an unlisted IP receives a403. Leave empty to disable IP filtering.API_INTERNAL_SSL_CERTPath to the server certificate that Traefik presents on the
API_INTERNAL_URLhostname. Its SAN must includeAPI_INTERNAL_URL.API_INTERNAL_SSL_KEYPath to the private key associated with the server certificate above.
API_INTERNAL_SSL_CAPath to the CA used to validate the client certificate presented by the caller (mTLS). This variable is what actually enables dedicated mTLS: when set, only clients presenting a certificate signed by this CA are accepted.
Note
The three API_INTERNAL_SSL_* variables are optional. If they are not
set, the route remains open but without a dedicated PKI. To obtain mTLS
with a PKI separate from the standard internal PKI, you must set at
least API_INTERNAL_SSL_CA (and provide a valid server certificate for
the hostname — see Required Certificates).
API_INTERNAL_HMACShared HMAC key, verified by the API at the application layer on
/api/internalcalls. Stored as a secret. This layer is independent of mTLS: a caller must present the correct HMAC in addition to satisfying the transport-layer controls.
Behaviour by Topology¶
Single-site deployment (infra_manager)¶
The /api/internal route is opened on the portal, on the
API_INTERNAL_URL hostname. The portal then relays the request to the API.
In this mode, PORTAL_URL must be set to an exact hostname. If the portal is
configured in catch-all mode (without PORTAL_URL), the dedicated route is
not created, because the catch-all router would conflict with the mTLS
authentication on the dedicated hostname.
Split deployment (api_manager + portal_manager)¶
The /api/internal route is opened only on api_manager, directly
on the API service. The portal_manager does not open the route.
Note
On api_manager, the standard API host already enforces mTLS with the
standard internal PKI. The dedicated API_INTERNAL_URL route adds, on
its own hostname, mTLS with the dedicated PKI (API_INTERNAL_SSL_CA),
allowing two distinct PKIs to coexist: one for regular API traffic, and one
for the internal API.
Blocking Access via the Public Channel¶
To ensure that /api/internal is reachable only via the dedicated
hostname, access to the /api/internal path is denied (403) on the
public hostname (portal, or standard API host depending on the topology).
Warning
If a legitimate component calls /api/internal via the public host
(rather than via API_INTERNAL_URL or directly on the container), this
block will prevent it. Verify the path your callers take before enabling
this behaviour.
Required Certificates¶
Two distinct elements are involved in mTLS:
- The server certificate (
API_INTERNAL_SSL_CERT/API_INTERNAL_SSL_KEY) This is what Traefik presents to the client. Its SAN must include
API_INTERNAL_URL, and it must be issued by an authority the caller trusts (the--cacerton the client side). If no certificate matching the hostname is provided, Traefik presents a self-signed certificate, and the client can only validate the connection by disabling verification (to be avoided).- The client validation CA (
API_INTERNAL_SSL_CA) This is the authority that must have signed the client certificate presented by the caller. A client certificate signed by a different authority (for example the standard internal PKI) will be rejected on this hostname.
Configuration Example¶
api_manager:
vars:
API_INTERNAL_ENABLED: true
API_INTERNAL_URL: "apiinternal.example.com"
API_INTERNAL_RESTRICT_IP: "10.0.0.0/8,192.168.10.20/32"
API_INTERNAL_SSL_CERT: "/local/path/apiinternal.crt"
API_INTERNAL_SSL_KEY: "/local/path/apiinternal.key"
API_INTERNAL_SSL_CA: "/local/path/apiinternal_ca.crt"
API_INTERNAL_HMAC: "<shared HMAC key>"
Verification¶
Once deployed, an authenticated call should reach the API without disabling TLS verification:
curl -v \
--cacert /path/ca.pem \
--cert /path/client.crt \
--key /path/client.key \
https://apiinternal.example.com/api/internal/users/reset-otp
Checkpoints during the handshake:
Request CERTon the server side: the server is requesting the client certificate (mTLS active);the server certificate presented matches
API_INTERNAL_URLand is validated by your--cacert(noself-signed certificatemessage);the connection succeeds; an application-level code (for example
401if the HMAC is missing or invalid) confirms the request reached the API.
Note
A 401 Unauthorized comes from the API (application HMAC check) and
not from Traefik: it means the transport layer (routing, mTLS, IP filtering)
succeeded and only the application authentication header is missing.
Key Points¶
API_INTERNAL_URLmust be distinct fromPORTAL_URLandAPI_DOCKER_NAME. Reusing an existing hostname causes a TLS option conflict on that hostname and the dedicated mTLS would not apply reliably.The dedicated mTLS and the standard internal PKI are independent: the same client certificate is not valid on both. Distribute to internal API callers a certificate signed by
API_INTERNAL_SSL_CA.IP filtering, mTLS, and HMAC are cumulative. For an administration endpoint, it is recommended to combine at least dedicated mTLS and HMAC.
In
infra_managermode,PORTAL_URLmust be an exact hostname for hostname-based isolation to work.