API gateway

The open source, self-hosted API gateway

Octelium is a free and open source, self-hosted zero trust access platform. Its HTTP and gRPC modes put an identity-aware proxy in front of your APIs: each request is authorized on its own method, path and body, the upstream credential stays in the Cluster, and plugins handle rate limiting, validation and manipulation.

  • Free and open source
  • Designed for self-hosting
  • REST and gRPC
service.yaml
An API ServiceEach API is a Service behind an identity-aware proxy, with its own upstream credential.
An API Service. Each API is a Service behind an identity-aware proxy, with its own upstream credential.
kind: Service
metadata:
  name: api
spec:
  mode: HTTP
  isPublic: true
  config:
    upstream:
      url: https://api.internal:8443
    http:
      auth:
        bearer:
          fromSecret: api-key
A Policy. Each request is authorized on its method, path and body, not only on a token.
kind: Policy
metadata:
  name: partners
spec:
  rules:
    - effect: ALLOW
      condition:
        all:
          of:
            - match: ctx.request.http.method == "GET"
            - match: ctx.request.http.path.startsWith("/v1/")
            - match: '"partners" in ctx.user.spec.groups'
A rate limit. A sliding-window rate limit is a plugin on the Service, keyed per Session by default.
kind: Service
metadata:
  name: api
spec:
  mode: HTTP
  isPublic: true
  config:
    upstream:
      url: https://api.internal:8443
    http:
      plugins:
        - name: minute-limit
          condition:
            matchAny: true
          rateLimit:
            limit: 120
            window:
              minutes: 1
service-private.yaml
A private upstreamAn API behind NAT in any environment, published without an inbound port beside it.
A private upstream. An API behind NAT in any environment, published without an inbound port beside it.
kind: Service
metadata:
  name: api
spec:
  mode: HTTP
  isPublic: true
  config:
    upstream:
      url: http://10.0.6.40:8080
gRPC as well as REST. The gRPC mode listens over HTTP/2 and decodes requests as gRPC for policy and auditing.
kind: Service
metadata:
  name: grpc-api
spec:
  mode: GRPC
  port: 8080
  isPublic: true
  config:
    upstream:
      url: https://grpc.internal:8443
Deployed by the Cluster. The upstream can be a container the Cluster deploys, scales and serves itself.
kind: Service
metadata:
  name: api
spec:
  mode: HTTP
  isPublic: true
  config:
    upstream:
      container:
        port: 8080
        image: ghcr.io/acme/api:3.1.0
        replicas: 4
policy-http.yaml
Method, path and bodyThe serialized JSON body is part of the request context, not only the method and path.
Method, path and body. The serialized JSON body is part of the request context, not only the method and path.
kind: Policy
metadata:
  name: bounded-writes
spec:
  rules:
    - effect: ALLOW
      condition:
        all:
          of:
            - match: ctx.request.http.method in ["POST", "PUT"]
            - match: ctx.request.http.path.startsWith("/v1/orders")
            - match: ctx.request.http.bodyMap.amount < 5000
gRPC services and methods. The package, service and method are decoded and available to a Policy.
kind: Policy
metadata:
  name: grpc-read-only
spec:
  rules:
    - effect: ALLOW
      condition:
        all:
          of:
            - match: ctx.request.grpc.service == "MainService"
            - match: ctx.request.grpc.method == "GetUser"
            - match: '"partners" in ctx.user.spec.groups'
Schema validation. A JSON schema plugin rejects malformed bodies before they reach the upstream.
kind: Service
metadata:
  name: api
spec:
  mode: HTTP
  isPublic: true
  config:
    upstream:
      url: https://api.internal:8443
    http:
      plugins:
        - name: validate-orders
          condition:
            match: ctx.request.http.path.startsWith("/v1/orders")
          jsonSchema:
            inline: <ORDER_JSON_SCHEMA>
            statusCode: 400
http-auth.yaml
An upstream API keyThe upstream credential stays in the Cluster and is injected after authorization.
An upstream API key. The upstream credential stays in the Cluster and is injected after authorization.
kind: Service
metadata:
  name: api
spec:
  mode: HTTP
  isPublic: true
  config:
    upstream:
      url: https://api.internal:8443
    http:
      auth:
        bearer:
          fromSecret: api-key
Mutual TLS. Client certificates for mTLS-protected upstreams are held and presented by the Cluster.
kind: Service
metadata:
  name: partner-api
spec:
  mode: HTTP
  isPublic: true
  config:
    upstream:
      url: https://partner.example.com
    tls:
      clientCertificate:
        fromSecret: partner-client-cert
Versions and accounts per request. Identity and request context select which upstream and which credential are used.
kind: Service
metadata:
  name: api
spec:
  mode: HTTP
  isPublic: true
  config:
    upstream:
      url: https://v1.internal:8443
    http:
      auth:
        bearer:
          fromSecret: v1-api-key
  dynamicConfig:
    configs:
      - name: v2
        upstream:
          url: https://v2.internal:8443
        http:
          auth:
            bearer:
              fromSecret: v2-api-key
    rules:
      - condition:
          match: '"beta" in ctx.user.spec.groups'
        configName: v2
credential.sh
OAuth2 for workloadsApplications authenticate with the standard client credentials flow, with no SDK.
OAuth2 for workloads. Applications authenticate with the standard client credentials flow, with no SDK.
octeliumctl create cred \
  --type oauth2 \
  --user microservice1 \
  api-cred

# The application then authenticates at
# https://<DOMAIN>/oauth2/token
# and sends the issued bearer access token.
Secretless workload identity. Workloads can authenticate with OIDC assertions issued by the platform running them.
kind: IdentityProvider
metadata:
  name: github-actions
spec:
  oidcIdentityToken:
    issuerURL: https://token.actions.githubusercontent.com
    audience: https://example.com
A Lua plugin. Lua and Envoy ExtProc plugins inspect and intentionally mutate requests and responses.
kind: Service
metadata:
  name: api
spec:
  mode: HTTP
  isPublic: true
  config:
    upstream:
      url: https://api.internal:8443
    http:
      plugins:
        - name: enrich
          condition:
            matchAny: true
          lua:
            inline: |
              function onRequest(ctx)
                local b = octelium.req.getRequestBody()
                octelium.req.setRequestBody(b)
              end
Visibility and auditing

Real-time visibility at the application layer

OpenTelemetry-ready, application-layer aware, structured auditing and visibility emitted to your OpenTelemetry OTLP receivers, where they can be exported to your log management and SIEM tools.

Read about visibility and auditing
AccessLogOTLP
ALLOWlinus@acme.compg-prod.dbPOSTGRESquery: select id, email from users limit 50
ALLOWci-runnerprod-k8sKUBERNETESverb=list resource=pods namespace=production
DENYagent-07tools-mcpMCPtools/call name=transfer policy=mcp-tools
ALLOWjane@acme.combastionSSHsession recorded, upstream user=deploy
ALLOWagent-07openaiLLMCHAT_COMPLETIONS model=gpt-5-mini tokens=1284
octeliumctl
octeliumctl apply ./cluster
$ octeliumctl apply ./cluster
Secret: api-key Updated
Secret: partner-client-cert Updated
IdentityProvider: github-actions Created
Policy: partners Created
Group: partners Created
Group: beta Updated
Service: api.apis Created
Service: grpc-api.apis Created
Service: partner-api.apis Created
Namespace: apis Created
Cluster Core resources successfully applied
 7 resources created
 3 resources updated

 ClusterConfig updated
Responsibilities

What an API gateway has to do, and what does each part

An API gateway is usually adopted for one reason and then asked to carry several others. These are the responsibilities that accumulate, next to the mechanism in an Octelium Cluster that handles each one.

  1. 01

    Knowing who is calling

    Every request carries an Octelium identity rather than a shared API key. Applications authenticate with the standard OAuth2 client credentials flow, access tokens or federated OIDC assertions, in any language and with no proprietary SDK.

    Read more
  2. 02

    Deciding per request

    Authorization runs on every request with CEL or Open Policy Agent, over the method, path, headers, query parameters and serialized JSON body, combined with identity, group membership and device attributes in the same expression.

    Read more
  3. 03

    Holding the upstream credential

    API keys, bearer tokens, basic authentication, OAuth2 client credentials, AWS SigV4 signing and mTLS client certificates are stored as Secrets and injected after a request is authorized, so callers never hold them.

    Read more
  4. 04

    Routing to the right backend

    Dynamic configuration selects the upstream, credential, headers, paths and plugins per request from identity and request context, which covers API versioning, tenant separation and staged rollouts on one URL.

    Read more
  5. 05

    Reshaping the request

    Request and response headers can be added, overridden, appended or removed, and path prefixes can be added, removed or replaced, without changing the upstream service.

    Read more
  6. 06

    Protecting the upstream

    Plugins provide sliding-window rate limiting backed by the Cluster's own store, JSON schema validation with a configurable failure response, response caching with a custom key, and direct responses that never reach the upstream at all.

    Read more
  7. 07

    Running your own logic

    Lua scripts and Envoy ExtProc servers run in the request path, before or after authorization, for enrichment, redaction, guardrails and intentional mutation of request and response bodies.

    Read more
  8. 08

    Recording what happened

    Each request produces an identity-aware AccessLog with the method, path, response code, gRPC package, service and method where applicable, streamed in real time over OpenTelemetry to your own log management and SIEM providers.

    Read more

Proxying a request is the easy part. What makes a gateway worth operating is that the identity, the decision, the credential and the record all live in one declarative configuration rather than in four systems.

Questions

Frequently asked

How do applications authenticate?
Through the standard OAuth2 client credentials flow against the Cluster's own token endpoint, or with an access token Credential used directly as a bearer token, sent in an Authorization: Bearer header or an X-Octelium-Auth header. Ordinary OAuth2 libraries in any language work without a proprietary SDK. Workloads can also authenticate secretlessly with federated OIDC assertions from the platform hosting them.
What can a policy actually see?
For HTTP, the method, path, headers, query parameters and the serialized JSON body through ctx.request.http.bodyMap. For gRPC, the package, service, method and full service name, alongside the underlying HTTP request. Those combine with identity, group membership, device posture, session type and time in the same CEL or Open Policy Agent expression, evaluated per request.
Does it do rate limiting and caching?
Yes, as plugins on the Service. Rate limiting is a global sliding window backed by the Cluster's Redis store, applied per Session by default with a custom key available through a CEL expression, and several limits can run together. Response caching takes a TTL and its own key expression. JSON schema validation and direct responses are plugins as well.
Can one URL serve several API versions or accounts?
Yes. Dynamic configuration selects the upstream, upstream credential, headers, paths and plugins per request from identity and request context, so a beta group can be routed to a newer backend with its own key while everyone else stays on the current one, under one Service and one set of Policies.
Does it handle gRPC?
Yes. The gRPC mode automatically listens over HTTP/2 as the protocol requires, and decodes requests so that the package, service and method are available for authorization and appear in the access logs alongside the underlying HTTP information.
Can it host the API as well as front it?
Yes. Managed containers deploy, scale and serve a container image as the Service upstream, on the Cluster's own data-plane nodes, with environment variables from Secrets, replica counts, resource limits, volumes and health probes. A Service can also load balance across several upstream endpoints.
Get started

Deploy Octelium on your own infrastructure in minutes

Free and open source. Self-hosted. No vendor lock-in.