As Octelium Clusters operate on top of Kubernetes, the Octelium Cluster can seamlessly function as an advanced Kubernetes ingress controller that provides L7-based routing, filtering, access control, visibility as well as automatic TLS termination, load balancing, request and response manipulation including paths, headers, and body content, including serialized JSON body content. It is important, however, to distinguish Octelium from conventional ingress controllers. Octelium does not utilize or monitor native Kubernetes Ingress resources or their associated rules. Instead, it employs its own Services to implement ingress functionality (read more about Services here). Because Octelium operates independently of the standard Kubernetes Ingress API, it can be deployed alongside traditional ingress controllers within the same cluster without conflict
A Simple Example
In this guide we are going to mostly assume that your internal Kubernetes services require no authentication and thus use anonymous Services (read more here).
If your internal Kubernetes service has the name my-k8s-svc in the default Kubernetes namespace and listening over the TCP/8080 port, then you can expose it as follows:
1kind: Service2metadata:3name: my-svc4spec:5mode: HTTP6isPublic: true7isAnonymous: true8config:9upstream:10url: http://my-k8s-svc.default.svc:8080
In addition to being able to use internal Kubernetes services as upstreams for your Octelium Service, Octelium also enables you to directly deploy, manage and scale your containerized applications and serve them as Services by reusing the underlying Kubernetes infrastructure that runs the Octelium Cluster and deploying your Dockerized images on top of it. (read more about managed containers here)
1kind: Service2metadata:3name: my-api4spec:5mode: HTTP6isPublic: true7isAnonymous: true8config:9upstream:10container:11port: 300012image: ghcr.io/<ORG>/<IMAGE>:<TAG>13replicas: 314credentials:15usernamePassword:16username: linus17password:18fromSecret: reg-password19resourceLimit:20cpu:21millicores: 200022memory:23megabytes: 4000
You can now apply the creation of the Service as follows (read more here):
octeliumctl apply /PATH/TO/SERVICE.YAML
Now your internal my-k8s-svc Kubernetes service is exposed publicly at the https://my-svc.<DOMAIN>.
You might want to have a look at Namespaces (read more here) to group and organize your Services' FQDNs.
Octelium Services are implemented as Kubernetes deployment with a single replica by default. You can horizontally scale up or down your Service as follows:
1kind: Service2metadata:3name: my-svc4spec:5mode: HTTP6isPublic: true7isAnonymous: true8config:9upstream:10url: http://my-k8s-svc.default.svc:808011deployment:12replicas: 5
Dynamic Routing
Octelium enables you to dynamically route to a specific upstream, not only based on request paths as in Kubernetes-native ingress controllers, but also based on request headers, method, query parameters, and even body content, including serialized JSON body content. You can use dynamic configuration (read more here) to define multiple configurations that can be chosen on a per-request basis via CEL/OPA policy-as-code rules. Here is a simple example:
1kind: Service2metadata:3name: my-api4spec:5mode: HTTP6isPublic: true7isAnonymous: true8dynamicConfig:9configs:10- name: v111upstream:12url: http://apiv1.default.svc:808013- name: v214upstream:15url: http://apiv2.default.svc:808016rules:17- condition:18match: 'ctx.request.http.path.startsWith("/v1")'19configName: v120- condition:21all:22of:23- match: 'ctx.request.http.path.startsWith("/v2")'24- match: ctx.request.http.method in ["POST", "GET", "PUT"]25- match: ctx.request.http.headers["user-agent"].toLower().contains("my-client")26- match: int(ctx.request.http.queryParams.page) < 10027configName: v228- condition:29matchAny: true30configName: v1
You can also simultaneously deploy multiple containers that correspond to multiple configurations (e.g. multiple API versions) and route among them on a per-request basis. Here is an example:
1kind: Service2metadata:3name: my-web-app4spec:5mode: WEB6dynamicConfig:7configs:8- name: c19upstream:10container:11port: 300012image: ghcr.io/org/image:v113- name: c214upstream:15container:16port: 300017image: ghcr.io/org/image:v218rules:19- condition:20match: ctx.request.http.path.startsWith("/v1")21configName: c122- condition:23match: ctx.request.http.path.startsWith("/v2")24configName: c2
Request and Response Manipulation
Octelium also enables you to modify and rewrite your requests and responses. That includes header manipulation, path manipulation, CORS and retries. Here is a detailed example:
1kind: Service2metadata:3name: my-svc4spec:5mode: HTTP6isPublic: true7isAnonymous: true8config:9upstream:10url: http://my-k8s-svc.default.svc:808011http:12header:13addRequestHeaders:14- key: X-Header-115value: VALUE-116removeRequestHeaders:17- X-Header-218addResponseHeaders:19- key: X-Header-320value: VALUE-321removeResponseHeaders:22- X-Header-423path:24removePrefix: /v125addPrefix: /v226enableRequestBuffering: true27body:28maxRequestSize: 10000029mode: JSON30retry:31maxRetries: 1032initialInterval:33milliseconds: 50034maxInterval:35seconds: 536maxElapsedTime:37seconds: 1238multiplier: 1.539statusCodes: [503, 429]40retryOnServerErrors: true41cors:42allowOriginStringMatch: ["https://example.com"]43allowMethods: "POST, GET, OPTIONS"44allowHeaders: "X-PINGOTHER, Content-Type"45exposeHeaders: "Content-Encoding, Kuma-Revision"46maxAge: "86400"47allowCredentials: true
You can also use Octelium's HTTP plugins, including Lua scripts and Envoy's ExtProc compliant servers, to sanitize and manipulate your request and responses (read more about HTTP plugins here) in arbitrarily complex ways. For example, you can use this to manipulate the JSON body content of requests/responses and call external APIs to implement geolocation-based access control and/or rate limiting. Here is an example:
1kind: Service2metadata:3name: my-svc4spec:5mode: HTTP6isPublic: true7isAnonymous: true8config:9upstream:10url: http://my-k8s-svc.default.svc:808011http:12plugins:13- name: minute-rate-limit14condition:15matchAny: true16ratelimit:17limit: 10018window:19minutes: 220- name: hour-rate-limit21condition:22matchAny: true23ratelimit:24limit: 100025window:26hours: 627- name: rate-limit-auth28condition:29match: ctx.request.http.path.startsWith("/auth")30ratelimit:31limit: 1032window:33minutes: 134- name: validate-users35condition:36all:37of:38- match: ctx.request.http.path == "/users"39- match: ctx.request.http.method in ["POST", "PUT"]40jsonSchema:41inline: <YOUR_JSON_SCHEMA>42- name: drop-php43condition:44match: ctx.request.http.path.toLower().endsWith(".php")45direct:46statusCode: 40047body:48inline: '{"error": "invalid path"}'49- name: lua-cleanup50condition:51matchAny: true52lua:53inline: |54function onRequest(ctx)55if strings.hasPrefix(ctx.request.http.path, "/apis/v0") then56octelium.req.exit(400)57end58end596061function onResponse(ctx)62octelium.req.deleteResponseHeader("X-Internal")63end
Access Control
So far, we have used an anonymous Service, which allows all access publicly over the internet. This could be useful for hosting and testing public websites, APIs, webhooks, etc. Since Octelium is a zero trust access platform, it is primarily designed for identity-based access control you might want to only restrict the Service access to the Cluster's Users who can access the Service after authenticating to a web-based IdentityProvider (e.g. OpenID Connect, SAML 2.0, GitHub OAuth2) via their browsers (read more here). You can do so by removing the isAnonymous field and adding Policies for who is allowed to access the Service (read more about Policies and access control here). Here is an example:
1kind: Service2metadata:3name: my-svc4spec:5mode: HTTP6isPublic: true7config:8upstream:9url: http://my-k8s-svc.default.svc:808010authorization:11inlinePolicies:12- spec:13rules:14- effect: ALLOW15condition:16all:17of:18- match: ctx.user.spec.email.endsWith("@example.com")19- match: ctx.request.http.path.startsWith("/apis")20- match: ctx.request.http.method in ["GET", "POST", "PUT", "DELETE"]21- match: ctx.request.http.uri == "/apis/users?name=john"22- match: ctx.request.http.queryParams.name == "john"23- match: ctx.request.http.headers["x-custom-header"] == "this-value"24- match: ctx.request.http.scheme == "http"25- match: string(ctx.request.http.body).toLower().contains("value1")26- match: ctx.request.http.bodyMap.key1 == "value1"
Moreover, Octelium also supports anonymous authorization where you can apply your Policies and InlinePolicies' rules in the anonymous mode. You can read more about anonymous authorization here.
Visibility
Octelium also provides OpenTelemetry-ready, application-layer L7 aware visibility and access logging in real time (see an example for HTTP here). You can read more about visibility here.
This was a very short guide to show you how to use Octelium to deploy, scale, route and provide dynamic zero trust secure access to your workloads. Here are a few more related features that you might be interested in:
- Routing not just by request paths, but also by header keys and values, request body content including JSON (read more here).
- Request/response header manipulation (read more here).
- Cross-Origin Resource Sharing (CORS) (read more here).
- gRPC mode (read more here).
- Secretless access to upstreams and injecting bearer, basic, or custom authentication header credentials (read more here).
- Exposing the API publicly for anonymous access (read more here).
- Application layer-aware ABAC access control via policy-as-code using CEL and Open Policy Agent (read more here).
- OpenTelemetry-ready, application-layer L7 aware auditing and visibility (read more here).