Octelium documentation · Latest
Self-Hosted VPN and Exit Nodes via SOCKS5
Octelium can very easily operate as your own self-hosted exit node and a lightweight self-hosted alternative to consumer VPNs via the SOCKS5 Service mode (read more about the SOCKS5 mode here). Your traffic leaves to the internet from a network that you control, whether it is the network of your Cluster, your home network behind NAT, a cloud VM in another region, or even the Tor network. Unlike a commercial VPN provider, there is no third party that sees your traffic, and unlike running a standalone SOCKS5 proxy exposed to the internet, every single connection is authenticated, authorized and audited by the Cluster. Octelium provides the following:
Exit via the Cluster's own network by deploying a SOCKS5 proxy as a managed container (read more about managed containers here) with secretless access, so that your Users never see or manage the proxy password.
Exit via any connected
octeliumclient from anywhere behind NAT (e.g. a home server, a Raspberry Pi, a laptop, a VM in a different cloud region, etc...) through the embedded SOCKS5 mode and the built-insocks5.octeliumService.Exit via the Tor network, including access to
.onionservices, by deploying a Tor client as a managed container.Identity-based, context-aware access control on a per-connection basis according to the requested destination host, port and address type via policy-as-code with CEL and OPA (read more about Policies and access control here).
Dynamic routing to different exit nodes according to identity as well as the requested destination (read more about dynamic configuration here).
OpenTelemetry-native, identity-based, application-layer aware visibility where the destination of every connection as well as the transferred bytes are logged in real time (read more here).
Per-application exit, where only the applications you explicitly configure (e.g. a browser profile,
curl, etc...) use the exit node instead of all the traffic of your host.GitOps-friendly declarative, programmable management (read more here).
The SOCKS5 mode currently supports only the SOCKS5 CONNECT command. In other words, only TCP-based traffic goes through the exit node, while UDP-based traffic such as QUIC/HTTP3 and WebRTC is not proxied. Web browsers simply use TCP-based HTTP/1.1 and HTTP/2 instead whenever they are configured to use a SOCKS5 proxy.
Exit via the Cluster
The simplest exit node is the Cluster itself. In this example, we deploy go-socks5-proxy, a small open source SOCKS5 server that's published as the serjs/go-socks5-proxy image and is built on a distroless image running as a non-root user, as a managed container. The traffic of your Users then leaves to the internet from the public IP address of the Cluster's nodes. For example, if your Cluster is installed on a cloud VPS in another country, then you effectively have your own personal VPN server located in that country.
The serjs/go-socks5-proxy container requires a username and a password by default. Instead of sharing that password with your Users, we store it in a Secret (read more about Secrets here) that's used both by the container itself as well as by the Service to authenticate to the container on behalf of your Users. First, we create the Secret with a randomly generated password as follows:
Now we create the Service as follows:
The PROXY_PASSWORD environment variable is set from the exit-password Secret (read more here), and the socks5.auth field instructs the Service to authenticate to the upstream SOCKS5 server via the same Secret (read more about secretless access for SOCKS5 here). Therefore, the password never leaves the Cluster.
Your Users never reach the managed container directly since all of their connections go through the Service. However, the managed container runs inside the Kubernetes cluster that runs the Cluster. Keeping the container password-protected prevents other workloads running inside the same Kubernetes cluster from using it as an open proxy.
You can now apply the creation of the Service as follows (read more here):
Once connected to the Cluster via octelium connect (read more here), you can test the exit node, for example, via curl as follows:
The returned IP address should now be the public IP address of your Cluster rather than your own.
The --socks5-hostname flag, as well as the socks5h:// scheme, instructs the SOCKS5 client to send the destination domain name to the proxy instead of resolving it locally. This way the domain name is resolved at the exit node side which prevents DNS leaks from your own network. This also enables the Service to see the requested domain name itself, which is required for domain-based access control and visibility. On the other hand, the --socks5 flag resolves the domain name locally and only sends the resolved IP address to the proxy.
You can also connect via the rootless mode and map the Service to your host's localhost (read more here) without having to run octelium connect as root as follows:
Many command-line tools, including curl, also respect the ALL_PROXY environment variable. This enables you to route all the requests of a shell session through the exit node as follows:
The serjs/go-socks5-proxy container is stateless. You can, therefore, increase the number of its replicas in order to scale it horizontally (read more here).
Web Browsers
You can now use the exit node from your web browser while the rest of your host's traffic is unaffected. For example, in Firefox, go to Settings, then Network Settings, choose Manual proxy configuration, set the SOCKS Host to localhost and the Port to 1080, choose SOCKS v5 and enable Proxy DNS when using SOCKS v5 in order to resolve domain names at the exit node side.
For Chromium-based browsers (e.g. Google Chrome, Brave, Microsoft Edge, etc...), you can launch a dedicated browser profile that always uses the exit node as follows:
The --host-resolver-rules flag prevents the browser from resolving domain names locally, while the --user-data-dir flag keeps your ordinary browser profile untouched.
Exit via Connected Clients
Exiting via the Cluster is not the only way. In many cases, you might want your traffic to exit from your own home network while you are traveling, or from a VM in a certain cloud region, or from an office machine. Octelium enables any connected octelium client to act as an exit node via the embedded SOCKS5 mode (read more here). In this mode, the SOCKS5 server runs inside the octelium client itself and the connections to the final destinations originate from the host running the client, even if that host is behind NAT without any open ports.
The socks5.octelium Service
The Cluster comes with a built-in socks5.octelium Service in the octelium Namespace that operates in the embedded SOCKS5 mode. It is automatically created upon the Cluster installation and its configuration looks as follows:
Since the Service does not explicitly set a port, it listens over the default SOCKS5 port 1080. Now, in order to turn some host, for example your home server, into an exit node, you only need to connect to the Cluster from that host via the --esocks5 flag (read more here) as follows:
You can also enable the embedded SOCKS5 server via the OCTELIUM_ESOCKS5 environment variable instead of the --esocks5 flag as follows:
By default, the embedded SOCKS5 server listens only over the private tunnel addresses of the connected Session. In other words, the embedded SOCKS5 server is not reachable from your local network, and is only reachable through the Cluster. The domain names requested by your Users are resolved by the exit node host itself.
A headless exit node is typically a WORKLOAD User (read more about User types here). You can create the User as follows:
And then create an authentication token Credential for it (read more here) as follows:
Now you can run the exit node anywhere. For example, you can run it as a Docker container (read more here) on your home server or Raspberry Pi as follows:
The above container does not need any additional Linux capabilities. Without the NET_ADMIN capability, the octelium client uses the unprivileged gVisor netstack mode (read more here), and the embedded SOCKS5 server listens from within that netstack while it reaches the final destinations through the container's own network.
You can also deploy an exit node inside any remote Kubernetes cluster, for example a managed Kubernetes cluster in a certain cloud region, via the official Helm chart (read more here) as follows:
To use a certain exit node, you need to set the name of its Session as the SOCKS5 username. You can list the Cluster's Sessions as follows:
The output of the command above should look as follows:
Now, from your own machine, you can exit via the home-server-x8k2qa Session as follows:
The returned IP address should now be the public IP address of your home network. You can also map the socks5.octelium Service to your localhost via the rootless mode as follows:
A Session name is not permanent. For example, a container that authenticates via the --auth-token flag upon every start creates a new Session, and therefore a new Session name, every time it restarts. Moreover, most web browsers do not natively support SOCKS5 username/password authentication, which the embedded mode requires in order to select the exit node. If you need a stable exit node address or want to use the exit node from your web browser, then you might want to serve a SOCKS5 server from behind NAT as shown below.
Restricting Embedded Exit Nodes
It's important to understand that any User who is authorized to access an embedded SOCKS5 Service can use any connected Session that serves embedded SOCKS5 as an exit node, as long as the User knows the Session's name. Octelium has no notion of a "superuser", so by default no User can access the socks5.octelium Service unless explicitly allowed by a Policy. However, if some of your Users are granted broad access, for example via the allow-all Policy (read more here), then they can use your exit nodes too.
Since socks5.octelium is a built-in Service that is managed by the Cluster itself, you might want to create your own embedded SOCKS5 Service with its own inline Policies instead. Here is an example where only the members of the family Group can use the embedded exit nodes:
Now members of the family Group can use the exit node as follows:
You can additionally deny access to the built-in socks5.octelium Service for everybody else via a DENY rule as follows:
You can then enforce such a Policy across the entire Cluster as a global Policy (read more here).
Note that we used the not condition (read more here) instead of negating the expression itself inside a match condition (i.e. !("family" in ctx.user.spec.groups)). A User that does not belong to any Group at all has no groups field. In such case, an expression that refers to that missing field never matches, while the not condition correctly matches.
Serving a SOCKS5 Proxy from Behind NAT
Instead of the embedded mode, you can also run any SOCKS5 server behind NAT and remotely serve it via a connected octelium client as an ordinary SOCKS5 Service (read more about remotely serving upstreams here). This way your Users use a stable Service name, without having to know any Session name, and they can use it from their web browsers too.
For example, you can run the serjs/go-socks5-proxy container on your home server and bind it only to its localhost as follows:
Now we store the same password in a Secret as follows:
And then create the Service whose upstream is served by the home-server User as follows:
Now the home server needs to connect to the Cluster and serve the Service via the --serve flag (read more here) as follows:
Now your Users can use the exit node via the stable address home:1080, for example, as follows:
You can have several connected octelium clients serving the same Service. In such case, the Service load balances the connections among all of them (read more here).
Exit via Tor
You can also deploy a Tor client as a managed container and use it as an exit node to the Tor network. In this example, we use tor-socks-proxy, an actively maintained, minimal open source Tor client image that's based on Alpine Linux. The container runs Tor as the unprivileged tor user and listens with its SOCKS5 port at 9150. Here is an example:
Tor's SOCKS5 port does not require any authentication. Therefore, we explicitly instruct the Service to use no authentication when connecting to the upstream via the noAuth field. We also set the Service port to 9050, the conventional port of the Tor SOCKS5 proxy, while the container itself listens over the port 9150.
The peterdavehello/tor-socks-proxy image is currently published for the amd64 architecture only. If your Cluster's data-plane nodes are ARM-based (e.g. a Raspberry Pi-based homelab Cluster), you need to build the image yourself from its open source Dockerfile and deploy it from your own container registry (read more about private registries here).
You can now apply the creation of the Service via octeliumctl apply as shown above. Note that Tor might need a few moments to bootstrap its circuits once the container starts. You can now test the exit node via the Tor Project's own check API as follows:
The output should look as follows:
You can also reach .onion services. For example, you can reach the DuckDuckGo onion service as follows:
.onion addresses can only be resolved by the Tor network itself. Therefore, you must use a SOCKS5 client that sends the domain name to the proxy (e.g. the --socks5-hostname flag or the socks5h:// scheme for curl, or enabling Proxy DNS when using SOCKS v5 in Firefox) rather than resolving it locally.
You can also force Tor to use exit relays from certain countries. Tor accepts any configuration option as a command-line argument that overrides the container's torrc configuration file, so you only need to override the container's command and arguments (read more here) as follows:
Restricting the exit relays to a few countries reduces the number of available relays and can weaken the anonymity properties of Tor. Moreover, it's important to understand that, unlike the Tor Browser, using Tor from an ordinary browser does not protect you against browser fingerprinting. Also note that the Cluster itself still authenticates your Users and logs the destinations of their connections. In other words, Tor here hides your Cluster's IP address from the destinations rather than hiding the identity of your Users from the Cluster.
Dynamic Configuration
You can also combine several exit nodes in a single Service and dynamically route each connection to a certain exit node according to the identity of the User as well as the requested destination (read more about dynamic configuration here). Here is an example of an all-in-one exit Service, which can replace the exit Service shown above, that automatically routes .onion destinations, as well as all the connections of the members of the tor-only Group, to Tor while all other connections exit via the Cluster:
Now any User can simply use the address exit:1080 and reach both the ordinary internet as well as .onion services without having to switch between proxies.
Access Control
An exit node is effectively an open door from your network to the internet. Octelium enables you to control access to it on a per-connection basis according to the SOCKS5 request information, which is available in ctx.request.socks5 (read more here), in addition to the identity and context of the User (read more about Policies and access control here). Here is a detailed example for the exit Service where only the members of the family Group are allowed to use the exit node, while connections to SMTP ports, which are commonly abused to send spam, as well as to private, loopback and link-local IP addresses (e.g. the 169.254.169.254 metadata endpoint of cloud providers) are denied:
The net.isPrivateIP and net.isIPInRange functions (read more about Octelium's CEL functions here) only work with IP addresses, which is why each expression first checks that the requested destination is not a domain name. Note that IP-based rules only apply when the requested destination is an IP address, since a domain name is resolved by the upstream SOCKS5 server itself. If you want to be even stricter, you can only allow domain-based destinations via the expression ctx.request.socks5.connect.addressType == "DOMAIN".
Authentication
HUMAN Users can seamlessly authenticate to the Cluster via their web browsers using IdentityProviders (read more here). There are currently 3 types:
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.
Furthermore, you can register a FIDO2 Authenticator (e.g. Yubikeys) in order to directly login later via Passkey (read more here) without having to use an IdentityProvider.
Visibility
Octelium also provides OpenTelemetry-ready, application-layer L7 aware visibility and access logging in real time (read more about visibility here). Each SOCKS5 connection emits a CONNECT access Log once it is authorized as well as a SESSION_END access Log once it ends (read more here). Here is an example of a SESSION_END access Log of a connection to a .onion service that was routed to the Tor exit node:
Here are a few more features that you might be interested in:
Exposing your homelab's DNS server, such as Pi-hole, to all of your connected Users (read more here).
Embedded SSH to remotely access the hosts of your exit nodes (read more here).
Persistent volumes, resource limits and probes for your managed containers (read more here).
Serving your exit nodes from inside remote Kubernetes clusters via Helm (read more here).
Application layer-aware ABAC access control via policy-as-code using CEL and Open Policy Agent (read more here).