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.

note

You can also check out the embedded SSH mode (read more here) 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) as follows:

octeliumctl create secret ssh1-password

Now, you define your Service as follows:

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

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) as follows:

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:

kind: Service metadata: name: ssh1 spec: mode: SSH port: 22 config: upstream: url: ssh://address-to-host ssh: user: root 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:

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 upstreamHostKey: key: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC+G/MMcPZTKlWX...

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

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

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

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

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:

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> enableSubsystem: true

Local Port Forwarding

By default, SSH local port forwarding (read more here) is disabled. You can, however, enable support for local port forwarding via the enableLocalPortForwarding field as follows:

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

{ "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:

kind: Service metadata: name: ssh1 spec: mode: SSH config: upstream: url: ssh://address-to-host ssh: # The rest of your configs 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:

kind: Service metadata: name: ssh1 spec: mode: SSH config: upstream: url: ssh://address-to-host ssh: # The rest of your configs visibility: enableSessionStdinRecording: true