# Self-Hosted VPN and Exit Nodes via SOCKS5

> Octelium documentation. Canonical page: <https://octelium.com/docs/octelium/latest/management/guide/service/homelab/self-hosted-vpn-exit-node-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](https://octelium.com/docs/octelium/latest/management/core/service/socks5.md)). 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](https://octelium.com/docs/octelium/latest/management/core/service/managed-containers.md)) with secretless access, so that your *Users* never see or manage the proxy password.
- Exit via any connected `octelium` client 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-in `socks5.octelium` *Service*.
- Exit via the Tor network, including access to `.onion` services, 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](https://octelium.com/docs/octelium/latest/management/core/policy.md)).
- Dynamic routing to different exit nodes according to identity as well as the requested destination (read more about dynamic configuration [here](https://octelium.com/docs/octelium/latest/management/core/service/dynamic-config.md)).
- 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](https://octelium.com/docs/octelium/latest/management/core/visibility.md)).
- 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](https://octelium.com/docs/octelium/latest/management/core/overview.md)).

> **Note:**
>
> 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](https://github.com/serjs/socks5-server), 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](https://octelium.com/docs/octelium/latest/management/core/secret.md#creating-a-secret)) 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:

```bash
octeliumctl create secret --value "$(openssl rand -hex 32)" exit-password
```

Now we create the *Service* as follows:

```yaml
kind: Service
metadata:
  name: exit
spec:
  mode: SOCKS5
  port: 1080
  config:
    upstream:
      container:
        image: serjs/go-socks5-proxy:latest
        port: 1080
        env:
          - name: PROXY_USER
            value: octelium
          # !mark(1:2)
          - name: PROXY_PASSWORD
            fromSecret: exit-password
        resourceLimit:
          cpu:
            millicores: 500
          memory:
            megabytes: 128
    # !mark(1:6)
    socks5:
      auth:
        usernamePassword:
          username: octelium
          password:
            fromSecret: exit-password
```

The `PROXY_PASSWORD` environment variable is set from the `exit-password` *Secret* (read more [here](https://octelium.com/docs/octelium/latest/management/core/service/managed-containers.md#using-a-secret-as-an-environment-variable)), 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](https://octelium.com/docs/octelium/latest/management/core/service/socks5.md#secretless-access)). Therefore, the password never leaves the *Cluster*.

> **Note:**
>
> 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](https://octelium.com/docs/octelium/latest/management/core/overview.md)):

```bash
octeliumctl apply /PATH/TO/SERVICE.YAML
```

Once connected to the *Cluster* via `octelium connect` (read more [here](https://octelium.com/docs/octelium/latest/user/cli/connect.md)), you can test the exit node, for example, via `curl` as follows:

```bash
curl --socks5-hostname exit:1080 https://checkip.amazonaws.com
```

The returned IP address should now be the public IP address of your *Cluster* rather than your own.

> **Note:**
>
> 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](https://octelium.com/docs/octelium/latest/user/cli/connect.md#rootless)) without having to run `octelium connect` as root as follows:

```bash
# Connect as non-root/unprivileged OS user
octelium connect -p exit:1080

# Now use the exit node at localhost
curl --socks5-hostname localhost:1080 https://checkip.amazonaws.com
```

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:

```bash
export ALL_PROXY=socks5h://localhost:1080
curl https://checkip.amazonaws.com
```

> **Note:**
>
> 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](https://octelium.com/docs/octelium/latest/management/core/service/managed-containers.md#scaling)).

### 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:

```bash
chromium --user-data-dir="$HOME/.config/chromium-exit" \
  --proxy-server="socks5://localhost:1080" \
  --host-resolver-rules="MAP * ~NOTFOUND , EXCLUDE localhost"
```

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](https://octelium.com/docs/octelium/latest/management/core/service/socks5.md#embedded-mode)). 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:

```yaml
kind: Service
metadata:
  name: socks5.octelium
  displayName: SOCKS5 Server for Embedded Access
spec:
  mode: SOCKS5
  config:
    socks5:
      isEmbeddedMode: true
```

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](https://octelium.com/docs/octelium/latest/user/cli/access.md#embedded-socks5)) as follows:

```bash
export OCTELIUM_DOMAIN=<DOMAIN>
octelium connect --esocks5
```

You can also enable the embedded SOCKS5 server via the `OCTELIUM_ESOCKS5` environment variable instead of the `--esocks5` flag as follows:

```bash
export OCTELIUM_ESOCKS5=true
octelium connect
```

> **Note:**
>
> 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](https://octelium.com/docs/octelium/latest/management/core/user.md#types)). You can create the *User* as follows:

```yaml
kind: User
metadata:
  name: home-server
spec:
  type: WORKLOAD
```

And then create an authentication token *Credential* for it (read more [here](https://octelium.com/docs/octelium/latest/management/core/credential.md#authentication-tokens)) as follows:

```bash
octeliumctl create cred --user home-server home-server-token
```

Now you can run the exit node anywhere. For example, you can run it as a Docker container (read more [here](https://octelium.com/docs/octelium/latest/user/cli/connect.md#containers)) on your home server or Raspberry Pi as follows:

```bash
docker run -d --name octelium-exit --restart unless-stopped \
  ghcr.io/octelium/octelium connect --domain <DOMAIN> --auth-token <AUTHENTICATION_TOKEN> --esocks5
```

> **Note:**
>
> 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](https://octelium.com/docs/octelium/latest/user/cli/connect.md#tunnel-implementation)), 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](https://octelium.com/docs/octelium/latest/management/guide/service/devops/octelium-helm-kubernetes.md)) as follows:

```bash
helm install exit-eu oci://ghcr.io/octelium/helm-charts/octelium --set octelium.domain=<DOMAIN> --set octelium.auth.token=<AUTHENTICATION_TOKEN> --set octelium.esocks5.enabled=true
```

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:

```bash
octeliumctl get sess
```

The output of the command above should look as follows:

```bash
         NAME        |    USER     |  TYPE  | EXPIRES IN | AGE | STATE  | CONNECTED
---------------------+-------------+--------+------------+-----+--------+------------
  home-server-x8k2qa | home-server | CLIENT | 83d        | 2h  | ACTIVE | True
  john-9o2ztp        | john        | CLIENT | 83d        | 6d  | ACTIVE | True
```

Now, from your own machine, you can exit via the `home-server-x8k2qa` *Session* as follows:

```bash
curl --socks5-hostname home-server-x8k2qa@socks5.octelium:1080 https://checkip.amazonaws.com
```

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:

```bash
octelium connect -p socks5.octelium:1080

curl --socks5-hostname home-server-x8k2qa@localhost:1080 https://checkip.amazonaws.com
```

> **Note:**
>
> 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](#serving-a-socks5-proxy-from-behind-nat).

### 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](https://octelium.com/docs/octelium/latest/management/core/user.md#privileged-users)), 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:

```yaml
kind: Service
metadata:
  name: esocks5
spec:
  mode: SOCKS5
  port: 1080
  config:
    socks5:
      isEmbeddedMode: true
  authorization:
    inlinePolicies:
      - spec:
          rules:
            - effect: ALLOW
              condition:
                match: '"family" in ctx.user.spec.groups'
```

Now members of the `family` *Group* can use the exit node as follows:

```bash
curl --socks5-hostname home-server-x8k2qa@esocks5:1080 https://checkip.amazonaws.com
```

You can additionally deny access to the built-in `socks5.octelium` *Service* for everybody else via a `DENY` rule as follows:

```yaml
kind: Policy
metadata:
  name: restrict-socks5-octelium
spec:
  rules:
    - effect: DENY
      condition:
        all:
          of:
            - match: ctx.service.metadata.name == "socks5.octelium"
            # !mark
            - not: '"family" in ctx.user.spec.groups'
```

You can then enforce such a *Policy* across the entire *Cluster* as a global *Policy* (read more [here](https://octelium.com/docs/octelium/latest/management/core/policy.md#global-policies)).

> **Note:**
>
> Note that we used the `not` *condition* (read more [here](https://octelium.com/docs/octelium/latest/management/core/policy.md#not)) 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](https://octelium.com/docs/octelium/latest/management/core/service/overview.md#remotely-via-a-connected-user)). 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:

```bash
docker run -d --name socks5 --restart unless-stopped -p 127.0.0.1:1080:1080 \
  -e PROXY_USER=octelium -e PROXY_PASSWORD=<PASSWORD> serjs/go-socks5-proxy:latest
```

Now we store the same password in a *Secret* as follows:

```bash
octeliumctl create secret --value <PASSWORD> home-password
```

And then create the *Service* whose upstream is served by the `home-server` *User* as follows:

```yaml
kind: Service
metadata:
  name: home
spec:
  mode: SOCKS5
  port: 1080
  config:
    upstream:
      url: tcp://localhost:1080
      # !mark
      user: home-server
    socks5:
      auth:
        usernamePassword:
          username: octelium
          password:
            fromSecret: home-password
```

Now the home server needs to connect to the *Cluster* and serve the *Service* via the `--serve` flag (read more [here](https://octelium.com/docs/octelium/latest/user/cli/connect.md#serving-services)) as follows:

```bash
export OCTELIUM_DOMAIN=<DOMAIN>
octelium connect --auth-token <AUTHENTICATION_TOKEN> --serve home
```

Now your *Users* can use the exit node via the stable address `home:1080`, for example, as follows:

```bash
curl --socks5-hostname home:1080 https://checkip.amazonaws.com
```

> **Note:**
>
> 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](https://octelium.com/docs/octelium/latest/management/core/service/overview.md#load-balancing)).

## 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](https://github.com/PeterDaveHello/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:

```yaml
kind: Service
metadata:
  name: tor
spec:
  mode: SOCKS5
  port: 9050
  config:
    upstream:
      container:
        image: peterdavehello/tor-socks-proxy:latest
        port: 9150
        resourceLimit:
          cpu:
            millicores: 1000
          memory:
            megabytes: 512
    socks5:
      auth:
        # !mark
        noAuth: true
```

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`.

> **Note:**
>
> 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](https://octelium.com/docs/octelium/latest/management/core/service/managed-containers.md#private-registry)).

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:

```bash
curl --socks5-hostname tor:9050 https://check.torproject.org/api/ip
```

The output should look as follows:

```json
{"IsTor":true,"IP":"203.0.113.42"}
```

You can also reach `.onion` services. For example, you can reach the DuckDuckGo onion service as follows:

```bash
curl --socks5-hostname tor:9050 -I https://duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad.onion/
```

> **Note:**
>
> `.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](https://octelium.com/docs/octelium/latest/management/core/service/managed-containers.md#command-and-arguments)) as follows:

```yaml
kind: Service
metadata:
  name: tor-eu
spec:
  mode: SOCKS5
  port: 9050
  config:
    upstream:
      container:
        image: peterdavehello/tor-socks-proxy:latest
        port: 9150
        # !mark(1:9)
        command:
          - /usr/bin/tor
        args:
          - -f
          - /etc/tor/torrc
          - --ExitNodes
          - "{de},{nl},{ch}"
          - --StrictNodes
          - "1"
    socks5:
      auth:
        noAuth: true
```

> **Note:**
>
> 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](https://octelium.com/docs/octelium/latest/management/core/service/dynamic-config.md)). 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*:

```yaml
kind: Service
metadata:
  name: exit
spec:
  mode: SOCKS5
  port: 1080
  dynamicConfig:
    configs:
      - name: direct
        upstream:
          container:
            image: serjs/go-socks5-proxy:latest
            port: 1080
            env:
              - name: PROXY_USER
                value: octelium
              - name: PROXY_PASSWORD
                fromSecret: exit-password
        socks5:
          auth:
            usernamePassword:
              username: octelium
              password:
                fromSecret: exit-password
      - name: tor
        upstream:
          container:
            image: peterdavehello/tor-socks-proxy:latest
            port: 9150
        socks5:
          auth:
            noAuth: true
    rules:
      - condition:
          any:
            of:
              # !mark(1:2)
              - match: ctx.request.socks5.connect.host.endsWith(".onion")
              - match: '"tor-only" in ctx.user.spec.groups'
        configName: tor
      - condition:
          matchAny: true
        configName: direct
```

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](https://octelium.com/docs/octelium/latest/management/core/service/socks5.md#access-control)), in addition to the identity and context of the *User* (read more about *Policies* and access control [here](https://octelium.com/docs/octelium/latest/management/core/policy.md)). 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:

```yaml
kind: Service
metadata:
  name: exit
spec:
  mode: SOCKS5
  port: 1080
  config:
    # The rest of the config as shown above
  authorization:
    inlinePolicies:
      - spec:
          rules:
            - effect: DENY
              condition:
                any:
                  of:
                    - match: ctx.request.socks5.connect.port in [25, 465, 587]
                    - match: ctx.request.socks5.connect.addressType != "DOMAIN" && net.isPrivateIP(ctx.request.socks5.connect.host)
                    - match: ctx.request.socks5.connect.addressType != "DOMAIN" && net.isIPInRange(ctx.request.socks5.connect.host, "127.0.0.0/8")
                    - match: ctx.request.socks5.connect.addressType != "DOMAIN" && net.isIPInRange(ctx.request.socks5.connect.host, "169.254.0.0/16")
            - effect: ALLOW
              condition:
                match: '"family" in ctx.user.spec.groups'
```

The `net.isPrivateIP` and `net.isIPInRange` functions (read more about Octelium's CEL functions [here](https://octelium.com/docs/octelium/latest/management/guide/cel.md#net)) 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](https://octelium.com/docs/octelium/latest/management/core/identity-providers.md)). There are currently 3 types:

- GitHub OAuth *IdentityProvider* as shown in detail [here](https://octelium.com/docs/octelium/latest/management/core/identity-providers.md#github-oauth2)
- OpenID Connect *IdentityProviders* (e.g. Okta, Auth0, etc...) as shown [here](https://octelium.com/docs/octelium/latest/management/core/identity-providers.md#openid-connect).
- SAML 2.0 *IdentityProviders* (e.g. Okta, Entra ID, etc...) as shown [here](https://octelium.com/docs/octelium/latest/management/core/identity-providers.md#saml-20).

Furthermore, you can register a FIDO2 *Authenticator* (e.g. Yubikeys) in order to directly login later via Passkey (read more [here](https://octelium.com/docs/octelium/latest/management/core/authenticator.md#passkey-login)) 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](https://octelium.com/docs/octelium/latest/management/core/visibility.md)). 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](https://octelium.com/docs/octelium/latest/management/core/service/socks5.md#visibility)). 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:

```yaml
{
  "apiVersion": "core/v1",
  "entry": {
    "common": {
      // Omitted for the sake of brevity of the example
    },
    # !mark(1:12)
    "info": {
      "socks5": {
        "type": "SESSION_END",
        "host": "duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad.onion",
        "port": 443,
        "addressType": "DOMAIN",
        "receivedBytes": "1874",
        "sentBytes": "70312",
        "upstreamHost": "upstream-svc-exit-tor",
        "upstreamPort": 9150
      }
    }
  },
  "kind": "AccessLog",
  "metadata": {
    // Omitted for the sake of brevity of the example
  }
}
```

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](https://octelium.com/docs/octelium/latest/management/guide/service/homelab/pihole.md)).
- Embedded SSH to remotely access the hosts of your exit nodes (read more [here](https://octelium.com/docs/octelium/latest/management/core/service/embedded-ssh.md)).
- Persistent volumes, resource limits and probes for your managed containers (read more [here](https://octelium.com/docs/octelium/latest/management/core/service/managed-containers.md)).
- Serving your exit nodes from inside remote Kubernetes clusters via Helm (read more [here](https://octelium.com/docs/octelium/latest/management/guide/service/devops/octelium-helm-kubernetes.md)).
- Application layer-aware ABAC access control via policy-as-code using CEL and Open Policy Agent (read more [here](https://octelium.com/docs/octelium/latest/management/core/policy.md)).
