Deploying Behind a Reverse Proxy

Modified on Tue, 14 Jul at 11:39 AM

Most production deployments run the transform server behind a reverse proxy, API gateway, or load balancer rather than exposing it directly. This article covers what to configure on each side and, since it is the question that comes up most once a proxy is in the picture, exactly where each kind of check (network, identity, authorization) happens in the request chain.

This article is about the SDK-side and proxy-side configuration. If a browser is showing a certificate warning, start with HTTPS, Certificates, and Browser Trust instead -- that is a separate concern from what is covered here.

Why put something in front of the transform server

Typical reasons to terminate traffic in front of the SDK rather than exposing run_server directly:

  • Centralizing TLS, mTLS, WAF rules, and rate limiting for many backend services instead of configuring them per transform server.
  • Running multiple transform servers, or multiple api_prefix instances of the same server, behind one public host.
  • Terminating SSO at a gateway or identity broker (Keycloak, an API gateway, Okta, etc.) and forwarding only the resulting bearer token/assertion to the SDK -- see Authentication.

TLS: terminate at the proxy or pass through

Two supported patterns:

Proxy terminates TLS, forwards plain HTTP internally. Configure the SDK's HTTP listener with ServerHTTPSettings(protocol="http") and enable trust_forwarded_headers so seed responses and discovery URLs reflect the original https:// scheme and host rather than the internal HTTP connection:

from maltego.server import run_server, MaltegoServerSettings
from maltego.model.server import ServerHTTPSettings


server_settings = MaltegoServerSettings(
    server_name="My Transform Server",
    ns="mycompany",
    author="dev@mycompany.com",
    trust_forwarded_headers=True,
    http_settings=ServerHTTPSettings(
        protocol="http",
        forwarded_allow_ips="10.0.0.1,10.0.0.0/24",  # your proxy's IP/CIDR, not "*"
    ),
)


run_server(settings=server_settings)

Scope forwarded_allow_ips to your proxy's address. Any client that can reach the SDK directly from an allowed IP can spoof x-forwarded-* headers, so this setting is a trust boundary, not a formality. See the "Seed URLs behind a reverse proxy or ingress" section of Server Configuration for the full precedence rules between trust_forwarded_headers, root_url, and full_host_url.

Proxy passes TLS through untouched (SNI passthrough). The SDK terminates TLS itself with its own certificate, and the proxy only forwards bytes without inspecting them. Configure ServerHTTPSettings(protocol="https") with certificate and private-key paths in this mode -- see Server Configuration's SSL/HTTPS section. trust_forwarded_headers is unnecessary here since the SDK sees the original connection directly.

Headers your proxy must forward

  • x-forwarded-proto and x-forwarded-host -- required when trust_forwarded_headers is enabled (TLS-terminating mode above).
  • Authorization and/or maltego-identity-authorization -- carry the upstream SSO credential or Maltego ID token. Do not strip these at the proxy; the SDK's auth layer reads them directly. See Authentication.
  • maltego-upstream-identity-method -- required alongside Authorization when the SDK validates an upstream SSO credential. See Authentication.

If any of these cross a browser CORS boundary (Maltego Graph Browser calling the transform server directly), they also need to be in your proxy's Access-Control-Allow-Headers allowlist -- see "CORS at the proxy" below.

CORS at the proxy

Configure CORS at the proxy or in the SDK (ServerHTTPSettings.cors_allowed_origins / cors_allowed_origin_regex, see Server Configuration), not both --duplicated or conflicting CORS headers from two layers are hard to debug.

If you terminate CORS at the proxy, an ingress policy can allow Maltego Graph Browser to discover and run transforms directly:

corsPolicy:
  allowOrigins:
    - regex: "https://app\\.maltego\\.com"
  allowMethods:
    - POST
    - GET
    - DELETE
    - OPTIONS
  allowHeaders:
    - authorization
    - content-type
    - maltego-client-capabilities
    - maltego-client-identifier
    - maltego-client-version
    - maltego-graph-id
    - maltego-identity-authorization
    - maltego-protocol-version
    - maltego-run-source
    - maltego-run-source-name
    - maltego-run-type
    - maltego-upstream-identity-method

The auth propagation headers are needed only when your deployment forwards Maltego ID or upstream SSO credentials through the browser path; maltego-upstream-identity-method specifically is SSO-only and is not required for Maltego ID credentials.

The header names above are needed for CORS allowlisting, but header values can carry operational context or credentials -- avoid logging or exposing them beyond what your deployment policy allows. These headers may change in future client versions; prefer an explicit allowlist and update it when the client contract changes.

An NGINX location handles the same preflight and response headers:

location / {
    if ($request_method = OPTIONS) {
        add_header Access-Control-Allow-Origin "https://app.maltego.com" always;
        add_header Access-Control-Allow-Methods "POST, GET, DELETE, OPTIONS" always;
        add_header Access-Control-Allow-Headers "$http_access_control_request_headers" always;
        add_header Vary "Origin, Access-Control-Request-Headers" always;
        return 204;
    }


    add_header Access-Control-Allow-Origin "https://app.maltego.com" always;
    add_header Vary "Origin" always;
    proxy_pass http://transform-server;
}

Where each check happens in the request chain

This is the part that is easy to get wrong: the proxy, the SDK's authentication layer, and TransformMiddleware all look like places to put "security logic," but they run at different points and see different information. For a transform-execution request, the order is:

  1. Your reverse proxy / gateway. TLS (or mTLS) termination, WAF rules, rate limiting, and rejection of malformed requests happen here, entirely outside the SDK. If your gateway is Keycloak or another broker handling end-user login/SSO, that login flow also happens here -- the SDK never sees a login form, only the resulting bearer token/assertion.
  2. uvicorn/ASGI. Malformed HTTP and unmatched routes/methods are rejected before any application code (FastAPI routing, auth, or TransformMiddleware) runs at all.
  3. Starlette/FastAPI-level middleware and routing (tracing, and CORS response headers if configured in the SDK rather than at the proxy), followed by route matching.
  4. The SDK's authentication dependency (AuthSettings) -- validates the JWT/OIDC/SAML credential the proxy forwarded. This runs as a FastAPI route dependency, resolved before the route's handler body executes. In strict mode, an invalid or missing credential is rejected right here -- nothing past this point runs, including TransformMiddleware. See Authentication.
  5. ``TransformMiddleware.before_transform`` / ``after_transform`` --runs only after step 4 has already populated context.identity and context.auth_claims, once per transform execution. This is the right layer for per-transform authorization, audit trails, and end-of-run logging, since it has both the resolved identity and the transform's input/output available. See Transform Middlewares for the full lifecycle and a worked authorization/audit example.
  6. The transform function. Business logic only.

Rule of thumb: network-level and pre-authentication concerns (TLS, mTLS, WAF, rate limiting, protocol validation, SSO login) belong at the edge, not in a TransformMiddleware. Per-request identity validation belongs in AuthSettings. Per-transform authorization/audit/logging that needs the resolved identity and transform context belongs in TransformMiddleware.

Was this article helpful?

That’s Great!

Thank you for your feedback

Sorry! We couldn't be helpful

Thank you for your feedback

Let us know how can we improve this article!

Select at least one of the reasons
CAPTCHA verification is required.

Feedback sent

We appreciate your effort and will try to fix the article