An Octelium Cluster is designed to be managed similarly to Kubernetes clusters. The main way to manage an Octelium Cluster is via the octeliumctl
CLI. If you're accustomed to kubectl
, you will feel right at home very quickly as it follows the same declarative philosophy enabling you to define your resources in one or more yaml
files and by just using a single command (i.e. octeliumctl apply
) you can apply all changes to the Cluster and synchronize its state (read more here). The declarative way enables you to grow your Cluster resources very easily in an organized and trackable way that can be stored in a Git repository where you can effortlessly update/rollback your Cluster state with a single command.
Creating our first Service
Each protected resource is represented in the Cluster by a Service. A Service is implemented by an identity-aware proxy (IaP) called Vigil abstracting all dynamic network-layer details of the protected resource (i.e. upstream) behind it and is capable of providing secure secret-less access that eliminates sharing and managing L7 credentials such as API keys and database passwords (read more about secret-less access here) for various protocols including HTTP, SSH, PostgreSQL, MySQL, among others besides protecting generic TCP/UDP-based applications (read more about Service modes here). The upstream of the Service can be:
- An internal resource running in any private network (e.g. on-prem, private cloud, your own laptop behind a NAT, etc...) which can be a static IPv4 or IPv6 address or a FQDN with dynamic endpoints as is the case with Kubernetes services for example. Furthermore, you can use an internal resource directly accessible from the private network where the Octelium Cluster (and its underlying Kubernetes cluster) is running.
- A publicly protected resource such as SaaS APIs, databases and SSH servers. See some examples (here and here).
- Octelium can also automatically deploy, scale and secure access for your containerized apps and microservices to be served as Services (read more about managed containers here).
We are going to define our first Service with the name first-service
whose upstream is the public URL https://www.google.com
:
1kind: Service2metadata:3name: first-service4spec:5mode: HTTP6config:7upstream:8url: https://www.google.com
Serving the public website https://www.google.com
as a Service, however, isn't particularly useful, beyond demonstrating that a Service can serve public or internal/private FQDNs that might point to dynamic upstreams with changing IP addresses—a capability remote access VPNs lack since they operate at the network layer/layer-3. We can do something a little more interesting, like deploying the nginx
container image and serve it as a Service (read more about managed containers here) as follows:
1kind: Service2metadata:3name: first-service4spec:5mode: HTTP6config:7upstream:8container:9port: 8010image: nginx:latest11replicas: 3
A Service can also provide "secret-less" access for the authorized Users to an upstream that requires an application-layer (L7) credential such as HTTP bearer access tokens (read more about secret-less access here). Octelium's application-layer awareness allows you to simply eliminate the need to manage and distribute such credentials, which are often long-lived and over-privileged in many practical cases, at any scale. Here is an example:
1kind: Service2metadata:3name: first-service4spec:5mode: HTTP6config:7upstream:8url: https://api.openai.com9http:10path:11addPrefix: /v112auth:13bearer:14fromSecret: openai-api-key
The highlighted openai-api-key
is the name of a Secret that actually represents the API access token, referenced in the Service by its name in order to not store sensitive data along with other Cluster configurations which can be stored in git repositories for example (read more about creating Secrets here).
Octelium's application-layer awareness is not exclusive to HTTP
-based Services. You can also define an SSH
Service that provides secret-less access without having to distribute and share SSH passwords or private keys (read more here). Here is an example:
1kind: Service2metadata:3name: ssh14spec:5mode: SSH6port: 20227config:8upstream:9url: ssh://address-to-host10ssh:11user: root12auth:13password:14fromSecret: ssh1-password15upstreamHostKey:16key: ssh-rsa AAAA...
Octelium also supports an "embedded SSH" mode, where you can serve SSH via an embedded SSH server that is running from within the octelium
client without having to rely on an existing SSH server on the host. This can be especially useful for constrained environments such as containers and IoT fleets (read more about the embedded SSH mode here).
Octelium currently supports several L7 aware modes: SSH, PostgreSQL, MySQL, DNS, gRPC, Web, Kubernetes in addition to the raw TCP and UDP modes.
And you can also serve any remote upstream from the host of any connected User to the Cluster (read more here) as follows:
1kind: Service2metadata:3name: first-service4spec:5mode: HTTP6config:7upstream:8url: https://www.google.com9user: john
This allows you to serve upstreams running behind NAT from anywhere (e.g. private clouds, on-prem, your own laptop, IoT devices, etc...). You can see a more detailed example here.
Now, to actually apply the creation of our Service first-service
, we use the octeliumctl apply
command as follows:
octeliumctl apply --domain <DOMAIN> /path/to/octelium/config/dir
If you're familiar with Kubernetes, you might have noticed the YAML visual resemblance between Octelium resources and Kubernetes resources and now you might be wondering whether Octelium resources are just Kubernetes resources or simply CRDs. The answer is no. Octelium and its resources are completely separate from Kubernetes. Octelium has its own gRPC-based API Server, and its resources are stored in the PostgreSQL data store via the Resource Server.
Since the Octelium CLIs (i.e. octelium
and octeliumctl
) are designed to work with multiple Clusters as defined by their own domains, you will have to add the --domain
flag to every command. It's much easier to define the domain as an environment variable in your shell once and then use any command without having to add the --domain
flag as follows:
export OCTELIUM_DOMAIN=<DOMAIN>
Or in Windows PowerShell as follows:
1$env:OCTELIUM_DOMAIN = "<DOMAIN>"
Now let's connect to our Cluster as follows:
# Connect via the detached modeoctelium connect -d# ORsudo -E octelium connect
In Windows, run your PowerShell as administrator.
Now that we're connected to the Cluster, we can access the Service first-service
simply using any tool that can talk HTTP, let's try curl
for example as follows:
curl first-service# This is equivalent tocurl first-service.local.<DOMAIN>
Why does this hostname first-service
work? Simply put, when you connect to a Cluster via the command octelium connect
, Octelium automatically configures your machine DNS and adds the suffix .local.<DOMAIN>
, which is the common suffix for all Services in the Cluster, to your machine's DNS search domains so you don't have to type the entire private FQDN yourself.
You can also run octelium connect
as a completely unprivileged process and map the Service first-service
to a localhost port as follows:
octelium connect -p first-service:8080
In the above example, we mapped the Service to the localhost port 8080
. Now we can access it as follows:
curl localhost:8080
You can read more about connecting to Clusters via the octelium
CLI and its more advanced options
here.
So far, in order to access our Service first-service
, Users will have to use the octelium
CLI and connect to the Cluster first. Octelium, however, also supports the client-less BeyondCorp mode, which enables you to securely expose an HTTP-based Service publicly in order to be accessed by authorized HUMAN
Users via their browsers and even by WORKLOAD
Users through standard OAuth2 client credentials authentication flow (read more here). You can very simply do so as follows:
1kind: Service2metadata:3name: first-service4spec:5mode: WEB6isPublic: true7config:8upstream:9container:10port: 8011image: nginx:latest
And now, authorized Users can publicly access the Service via their browsers at the public URL https://first-service.<DOMAIN>
Octelium can also fully expose a Service for anonymous access. This allows you to effectively use Octelium as a self-hosted PaaS or a hosting platform where you can publicly expose HTTP-based Services to the public internet. Such Services' upstreams might be running anywhere or be deployed as managed containers as we have seen earlier. You can read more about the anonymous access mode here. Here is an example:
1kind: Service2metadata:3name: first-service4spec:5mode: WEB6isPublic: true7isAnonymous: true8config:9upstream:10container:11port: 8012image: nginx:latest
You can also use dynamic configuration in order to, for example, route to different upstreams and/or set different upstream credentials mapping to different upstream accounts and permissions, set different request/response headers, etc... depending on the request's identity and/or context (read more about dynamic configuration here). Here is a simple example that can be used for an API gateway use case (read more here):
1kind: Service2metadata:3name: my-api4spec:5mode: HTTP6isPublic: true7dynamicConfig:8configs:9- name: v110upstream:11url: https://apiv1.example.com12http:13path:14removePrefix: /v115- name: v216upstream:17url: https://apiv2.example.com18http:19path:20removePrefix: /v221rules:22- condition:23match: ctx.request.http.path.startsWith("/v1")24configName: v125- condition:26match: ctx.request.http.path.startsWith("/v2")27configName: v2
Dynamic configuration can also be used for other layer-7 aware modes such as SSH
, POSTGRES
and MYSQL
as well as for TCP
and UDP
. For example, you can dynamically force certain Users to log in as certain SSH users based on identity or context via policy-as-code. For PostgreSQL or MySQL, you can enforce Users to use certain database users/passwords as well as different databases and even upstreams based on identity and context.
Creating a User
There are 2 types of Users (You can read in detail about User management here): HUMAN
Users and WORKLOAD
Users which can be used by non-human entities (e.g. servers, VMs and microVMs, containers, applications, IoT devices, etc...).
Both User types can use the private client-based ZTNA mode, which acts as a zero-config VPN from the User's perspective where Users can address Services via stable private FQDNs and hostnames assigned by the Cluster. Both User types can also securely access publicly exposed HTTP-based Services via the client-less, public BeyondCorp mode (read more here) as follows:
HUMAN
Users can access web-based Services via their browsers without having to install clients on their machines.WORKLOAD
Users can use the OAuth2 client credentials flow enabling your applications written in any programming language to access any publicly exposed HTTP-based Service (e.g. HTTP and gRPC APIs, Kubernetes API servers, etc...) solely via standard OAuth2 libraries without having to use any special SDKs or install any clients (read more here). Moreover, Golang-based applications can use the official Golang SDK (read more here) to control the Cluster and access its Services.
We are now going to create our first User for a friend in order to be able to connect to our Cluster and access our Service first-service
. As always, we add a new yaml
file, in this example users.yaml
, in the same directory (i.e. /path/to/octelium/config/dir
) dedicated to Users.
Now we can define our User john
as follows:
And to actually apply the creation of our User, we use the octelium apply
command as follows:
octeliumctl apply /path/to/octelium/config/dir
User Authentication
HUMAN
Users can use their emails to authenticate to the Cluster via web browsers using IdentityProviders. There currently 3 methods:
- GitHub OAuth IdentityProvider as shown in detail here
- OpenID Connect IdentityProviders (e.g. Okta, Auth0, etc...) as shown here.
- SAML 2.0 IdentityProviders (e.g. Okta, Entra ID, etc...) as shown here.
For WORKLOAD
Users, they can authenticate themselves via the octelium login
or octeliumctl login
commands using various ways:
- Authentication token Credentials (read more here)
- OAuth2 client credentials (read more here)
- "Secret-less" OpenID Connect identity assertions (read more here).
- Access tokens directly issued and used as bearer authentication tokens (read more here).
A User can interact with the Cluster and access its Services only through a valid Session that is automatically created by the Cluster upon a valid authentication via an authentication token, OAuth2 client credential authentication, or an IdentityProvider. A User needs to periodically re-authenticate to keep the Session valid until it eventually expires. You can read more about Session management here.
Access Control
Now, while our friend john
can actually connect to our Cluster, he still cannot access the Service first-service
unless we explicitly allow him via a Policy. For example, we can allow john
to access everything in our Cluster by attaching the allow-all
Policy as follows:
1kind: User2metadata:3name: john4spec:5type: HUMAN67authorization:8policies: ["allow-all"]
This allow-all
Policy is automatically created during the Cluster installation and you can modify/delete it, whenever you want to.
Allowing john
to access everything in the Cluster, however, does not sound like a very good idea since we seek to only grant access our first-service
Service. For example, we can, instead, define an inline Policy in our Service to allow anybody whose email belongs to our domain example.com
to access first-service
as follows:
1kind: Service2metadata:3name: first-service4spec:5mode: HTTP6config:7upstream:8url: https://example.com9authorization:10inlinePolicies:11- spec:12rules:13- effect: ALLOW14condition:15match: ctx.user.spec.email.endsWith("@example.com")
As you can see, you are free to either define your access control rules through inline Policies or standalone, reusable Policies that can be attached to any Service, Namespace, User, Group, Device or Session. Here is how to re-define the above inline Policy as a standalone Policy that can be used and attached by other resources:
1kind: Policy2metadata:3name: first-policy4spec:5rules:6- effect: ALLOW7condition:8match: ctx.user.spec.email.endsWith("@example.com")
Now we attach it to our Service as follows:
1kind: Service2metadata:3name: first-service4spec:5mode: HTTP6config:7upstream:8url: https://example.com9authorization:10policies: ["first-policy"]
We can also define a Policy for a whole set of Users. This is what a Group is for, among other functionalities (read more about Groups here). Let's define our first Group friends
and attach john
to it.
1kind: Group2metadata:3name: friends4spec:5authorization:6policies: ["first-policy"]
Now let us add friends
as one of john
's Groups as follows:
1kind: User2metadata:3name: john4spec:5type: HUMAN6groups: ["friends"]
Now, any User belonging to the friends
Group will be automatically subjected to all of its Policies and inline Policies' rules.
Octelium enables you to seamlessly define dynamic, fine-grained L7-aware Policies. For example, you can control access based on the HTTP request path, method, query parameters, and even serialized JSON body content (read more here). Here is an example:
1kind: Service2metadata:3name: first-service4spec:5mode: HTTP6config:7upstream:8url: https://example.com9authorization:10inlinePolicies:11- spec:12rules:13- effect: ALLOW14condition:15all:16of:17- match: ctx.request.http.method in ["GET", "POST", "PUT", "DELETE"]18- match: ctx.request.http.path.startsWith("/apis")19- match: ctx.request.http.uri == "/apis/users?name=john"20- match: ctx.request.http.queryParams.name == "john"21- match: ctx.request.http.headers["x-custom-header"] == "this-value"22- match: ctx.request.http.scheme == "http"23- match: string(ctx.request.http.body).toLower().contains("value1")24# JSON body map25- match: ctx.request.http.bodyMap.key1 == "value1"
Access control is the essence of the zero trust security model. This example is just the simplest use case of what you can do with Octelium's scalable, identity-based, fine-grained, context-aware, L7-aware access control system. Octelium supports defining Policy rules in Common Expression Language (CEL) and Open Policy Agent (OPA), rule priorities, nested conditions and extending access control by adding attributes to your different resources (e.g. Users, Groups, Services, etc...) from information provided by external tools such as IAM platforms, SIEM tools, threat intelligence tools, incident alerting and on-call management tools, etc.... You can read more in detail about Policies and access control here.
What Now?
This was just a quick guide to show you the main different features of Octelium. Octelium's architecture is designed to be flexible enough to be used as a Zero Trust Network Access (ZTNA) solution, a complete solution for secure tunnels, an API gateway (read more here), an AI gateway (read more here) and even can be used as a more advanced Kubernetes ingress/load balancer alternative as well as a self-hosted PaaS-like deployment and hosting platform to deploy, scale and provide secure or public anonymous access for your containerized applications such as Vite.js/Next.js/Astro web apps (see more here).