# SSH

> Octelium documentation. Canonical page: <https://octelium.com/docs/octelium/latest/management/core/service/ssh>.

Setting the *Service* mode to `SSH` enables it to operate in the application-layer aware SSH mode. This mode enables you to provide secretless access for *Users* to the protected upstream SSH server without having to share the upstream's password or private key. This mode also provides you with clear application-layer aware visibility where your SSH sessions are logged and audited in real-time.

> **Note:**
>
> You can read more about accessing `SSH`-based *Services* via the client-based mode [here](https://octelium.com/docs/octelium/latest/user/cli/access.md#ssh).

> **Note:**
>
> You can also check out the embedded SSH mode (read more [here](https://octelium.com/docs/octelium/latest/management/core/service/embedded-ssh.md)) where you can seamlessly provide secretless SSH access via SSH servers that are embedded from within connected `octelium` clients to serve directly SSH to *Users* without the need of SSH servers running on the client's host.

## Secretless Access

Secretless access enables you to provide secretless access for authorized *Users* to `SSH`-based *Service* by automatically injecting passwords and private keys to authenticate to the upstream SSH server, and force the *User* to connect to the upstream as a specific SSH user.

### Password

Here is an example where the upstream SSH server is accessed through a password. First, you need to create a *Secret* to store the password of your upstream SSH server (read more [here](https://octelium.com/docs/octelium/latest/management/core/secret.md#creating-a-secret)) as follows:

```bash
octeliumctl create secret ssh1-password
```

Now, you define your *Service* as follows:

```yaml
kind: Service
metadata:
  name: ssh1
spec:
  mode: SSH
  port: 2022
  config:
    upstream:
      url: ssh://address-to-host
    ssh:
      user: root
      # !mark(1:3)
      auth:
        password:
          fromSecret: ssh1-password
      # This is the upstream SSH server public key
      upstreamHostKey:
        key: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC+G/MMcPZTKlWX...
```

> **Note:**
>
> For internal/private upstream SSH servers behind NAT, you need to remotely serve them via a connected `octelium` client or container as discussed [here](https://octelium.com/docs/octelium/latest/management/core/service/overview.md#remotely-via-a-connected-user).

### Private Key

Here is an example where the upstream SSH server is accessed through a private key. First, you need to create a *Secret* to store the private key of the upstream SSH server (read more [here](https://octelium.com/docs/octelium/latest/management/core/secret.md#creating-a-secret)) as follows:

```bash
octeliumctl create secret ssh1-private-key --file ~/.ssh/id_ed25519
# OR
octeliumctl create secret ssh1-private-key -f ~/.ssh/id_ed25519
```

Now, you define your *Service* as follows:

```yaml
kind: Service
metadata:
  name: ssh1
spec:
  mode: SSH
  port: 22
  config:
    upstream:
      url: ssh://address-to-host
    ssh:
      user: root
       # !mark(1:3)
      auth:
        privateKey:
          fromSecret: ssh1-private-key
      # This is the upstream SSH server public key
      upstreamHostKey:
        key: ssh-rsa AAA...
```

> **Note:**
>
> If you do not explicitly set a value for the `user` field, then the value provided by the downstream client will be used.

## Upstream Host Key

As shown above, the upstream SSH server public key needs to be provided in the OpenSSH public key format `<key-type> <base64-encoded-key-data> [comment]` to be verified by the *Service* SSH client upon connecting to upstream. Here is an example:

```yaml
kind: Service
metadata:
  name: ssh1
spec:
  mode: SSH
  port: 2022
  config:
    upstream:
      url: ssh://address-to-host
    ssh:
      user: root
      auth:
        password:
          fromSecret: ssh1-password
      # !mark(1:2)
      upstreamHostKey:
        key: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC+G/MMcPZTKlWX...
```

You can, however, disable checking the upstream's host key via `insecureIgnoreHostKey` field as follows:

```yaml
kind: Service
metadata:
  name: ssh1
spec:
  mode: SSH
  port: 2022
  config:
    upstream:
      url: ssh://address-to-host
    ssh:
      user: root
      auth:
        password:
          fromSecret: <SECRET_NAME>
      upstreamHostKey:
        #!mark
        insecureIgnoreHostKey: true
```

## Access Control

You can control access based on the SSH request information. Such information are stored in `ctx.request.ssh` where it contains the requested SSH user. Here is an example:

```yaml
kind: Service
metadata:
  name: svc1
spec:
  mode: SSH
  port: 2022
  config:
    upstream:
      url: ssh://address-to-host
  # rest of config...
  authorization:
    inlinePolicies:
      - spec:
          rules:
            - effect: ALLOW
              condition:
              # !mark
                match: ctx.request.ssh.connect.user != "root"
```

> **Note:**
>
> You do not actually need to control access by checking against the SSH user (e.g. `root`) since you already can override the SSH user in the *Service* configuration as illustrated above regardless of the SSH user value provided by the *User*. You can also use dynamic configuration in order to map different *Users* to different credentials and/or different SSH users. You can read more about dynamic configuration [here](https://octelium.com/docs/octelium/latest/management/core/service/dynamic-config.md)

## Dynamic Configuration

You can use dynamic configuration in order to, for example, route to different upstreams and/or set different users/passwords depending on the request's context (read more about dynamic configuration [here](https://octelium.com/docs/octelium/latest/management/core/service/dynamic-config.md)). Here is a simple example where *Users* belonging to the `ops` *Group* are logged in as `root` SSH users, while the rest are logged in as some unprivileged `usr` SSH user.

```yaml
kind: Service
metadata:
  name: ssh1
spec:
  mode: SSH
  dynamicConfig:
    configs:
    - name: root
      upstream:
        url: ssh://address-to-host
      ssh:
        user: root
        auth:
          password:
            fromSecret: root-password
        upstreamHostKey:
          key: ssh-rsa AAA....
    - name: usr
      upstream:
        url: ssh://address-to-host
      ssh:
        user: usr
        auth:
          password:
            fromSecret: usr-password
        upstreamHostKey:
          key: ssh-rsa AAA....
    rules:
    - condition:
        match: '"ops" in ctx.user.spec.groups'
      configName: root
    - condition:
        matchAny: true
      configName: usr
```

## Subsystems

By default, SSH subsystem requests (e.g. SFTP) are disabled. You can, however, enable support for subsystems via the `enableSubsystem` field as follows:

```yaml
kind: Service
metadata:
  name: ssh1
spec:
  mode: SSH
  port: 2022
  config:
    upstream:
      url: ssh://address-to-host
    ssh:
      user: root
      auth:
        password:
          fromSecret: <SECRET_NAME>
      # !mark
      enableSubsystem: true
```

## Local Port Forwarding

By default, SSH local port forwarding (read more [here](https://www.ssh.com/academy/ssh/tunneling-example#local-forwarding)) is disabled. You can, however, enable support for local port forwarding via the `enableLocalPortForwarding` field as follows:

```yaml
kind: Service
metadata:
  name: ssh1
spec:
  mode: SSH
  port: 2022
  config:
    upstream:
      url: ssh://address-to-host
    ssh:
      user: root
      auth:
        password:
          fromSecret: <SECRET_NAME>
      # !mark
      enableLocalPortForwarding: true
```

## Visibility

The *Service* emits access logs in real time to the audit collector. Each log provides SSH application-layer aware information which includes full session recording as well as extracting `exec` requests and port forwarding session events. Here is an example:

```json
{
  "apiVersion": "core/v1",
  "kind": "AccessLog",
  "metadata": {
    "id": "f81f-uvw5-ltpjsqrmamkfwkesx8yf1qds-yqxy-ykr2",
    "createdAt": "2025-09-10T22:20:12.539582802Z",
    "actorRef": {
      "apiVersion": "core/v1",
      "kind": "Session",
      "uid": "b1bc6aaa-df51-456d-aa37-b77377ea26f0",
      "name": "root-1x09ce",
      "resourceVersion": "019935b6-5aaa-791f-b0bb-34668298c1fa"
    },
    "targetRef": {
      "apiVersion": "core/v1",
      "kind": "Service",
      "uid": "2898082e-7eab-4250-ad1e-694d8f995e74",
      "name": "ssh-h01.default",
      "resourceVersion": "019935b6-5acf-7a00-97d2-69ca402d873f"
    }
  },
  "entry": {
    "common": {
      "startedAt": "2025-09-10T22:19:56.701591026Z",
      "endedAt": "2025-09-10T22:20:12.539584852Z",
      "status": "ALLOWED",
      "mode": "SSH",
      "sessionRef": {
        "apiVersion": "core/v1",
        "kind": "Session",
        "uid": "b1bc6aaa-df51-456d-aa37-b77377ea26f0",
        "name": "root-1x09ce",
        "resourceVersion": "019935b6-5aaa-791f-b0bb-34668298c1fa"
      },
      "userRef": {
        "apiVersion": "core/v1",
        "kind": "User",
        "uid": "d72a39da-6f1c-43f7-ad75-dbaf76111b10",
        "name": "root",
        "resourceVersion": "019934c7-3d99-7864-8ccf-abe9eadfe023"
      },
      "serviceRef": {
        "apiVersion": "core/v1",
        "kind": "Service",
        "uid": "2898082e-7eab-4250-ad1e-694d8f995e74",
        "name": "ssh-h01.default",
        "resourceVersion": "019935b6-5acf-7a00-97d2-69ca402d873f"
      },
      "namespaceRef": {
        "apiVersion": "core/v1",
        "kind": "Namespace",
        "uid": "2073e6f7-24c2-49d2-b0df-e5cd3636d82c",
        "name": "default",
        "resourceVersion": "019934c7-6d7a-73cd-a510-dfadfdfa6682"
      },
      "regionRef": {
        "apiVersion": "core/v1",
        "kind": "Region",
        "uid": "85477de2-67d3-48ed-bda7-6c914489badf",
        "name": "default"
      },
      "connectionID": "og0t-9vd4-m6ancokp4o13icnn0qlf05kv-0ph9-mkxf",
      "sessionID": "og0t-9vd4-m6ancokp4o13icnn0qlf05kv-0ph9-mkxf-rnfoex",
      "sequence": 39
    },
    "info": {
      "ssh": {
        "type": "SESSION_RECORDING",
        "sessionRecording": {
          "type": "STDOUT",
          "data": "ABCDEF...."
        }
      }
    }
  }
}
```

As you can see in the above example, the type of this `SSH` access *Log* is a `SESSION_RECORDING`. The `SSH` mode has currently 10 *Log* types: `START`, `END`, `DIRECT_TCPIP_START`, `DIRECT_TCPIP_END`, `SESSION_START`, `SESSION_END`, `SESSION_RECORDING`, `SESSION_REQUEST_SHELL`, `SESSION_REQUEST_EXEC`, `SESSION_REQUEST_SUBSYSTEM` and some of these types include different detailed information according to their type. You can read more in the API reference.

## Visibility Options

You can disable the SSH session recording altogether via the `disableSessionRecording` field as follows:

```yaml
kind: Service
metadata:
  name: ssh1
spec:
  mode: SSH
  config:
    upstream:
      url: ssh://address-to-host
    ssh:
      # The rest of your configs
      #!mark(1:2)
      visibility:
        disableSessionRecording: true
```

By default, *Vigil* does not log stdin as it is usually echoed by stdout, unless for the case of non-interactive sessions. You can, however, explicitly enable STDIN recording as follows:

```yaml
kind: Service
metadata:
  name: ssh1
spec:
  mode: SSH
  config:
    upstream:
      url: ssh://address-to-host
    ssh:
      # The rest of your configs
      #!mark(1:2)
      visibility:
        enableSessionStdinRecording: true
```
