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.
You can read more about accessing SSH-based Services via the client-based mode here.
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-passwordNow, 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...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_ed25519Now, 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...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: trueAccess 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"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: usrSubsystems
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: trueLocal 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: trueVisibility
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: trueBy 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