SSH

Setting the Service mode to SSH enables it to operate in the application-layer aware SSH mode. This mode enables you to provide secret-less 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 also check out the embedded SSH mode where you can seamlessly provide secret-less 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.

Secret-less Access

Password

Here is an example where the upstream SSH server is accessed through a password.

1
kind: Service
2
metadata:
3
name: ssh1
4
spec:
5
mode: SSH
6
port: 2022
7
config:
8
upstream:
9
url: ssh://address-to-host
10
ssh:
11
user: root
12
auth:
13
password:
14
fromSecret: <SECRET_NAME>
15
upstreamHostKey:
16
key: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC+G/MMcPZTKlWX...

If you want to accept any host key from the upstream, which is NOT recommended, you can use the insecureIgnoreHostKey option as follows:

1
kind: Service
2
metadata:
3
name: ssh1
4
spec:
5
mode: SSH
6
port: 2022
7
config:
8
upstream:
9
url: ssh://address-to-host
10
ssh:
11
user: root
12
auth:
13
password:
14
fromSecret: <SECRET_NAME>
15
upstreamHostKey:
16
insecureIgnoreHostKey: true

Private Key

Here is an example where the upstream SSH server is accessed through a private key.

1
kind: Service
2
metadata:
3
name: ssh1
4
spec:
5
mode: SSH
6
port: 22
7
config:
8
upstream:
9
url: ssh://address-to-host
10
ssh:
11
user: root
12
auth:
13
privateKey:
14
fromSecret: <SECRET_NAME>
15
upstreamHostKey:
16
key: ssh-rsa AAA...

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:

1
kind: Service
2
metadata:
3
name: svc1
4
spec:
5
mode: SSH
6
port: 2022
7
config:
8
upstream:
9
url: ssh://address-to-host
10
# rest of config...
11
authorization:
12
inlinePolicies:
13
- spec:
14
rules:
15
- effect: ALLOW
16
condition:
17
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.

1
kind: Service
2
metadata:
3
name: ssh1
4
spec:
5
mode: SSH
6
dynamicConfig:
7
configs:
8
- name: root
9
upstream:
10
url: ssh://address-to-host
11
ssh:
12
user: root
13
auth:
14
password:
15
fromSecret: root-password
16
upstreamHostKey:
17
key: ssh-rsa AAA....
18
- name: usr
19
upstream:
20
url: ssh://address-to-host
21
ssh:
22
user: usr
23
auth:
24
password:
25
fromSecret: usr-password
26
upstreamHostKey:
27
key: ssh-rsa AAA....
28
rules:
29
- condition:
30
match: '"ops" in ctx.user.spec.groups'
31
configName: root
32
- condition:
33
matchAny: true
34
configName: usr

Visibility

The Service emits access logs in real time to the audit collector. Each log provides SSH application-layer aware information about the request such as the request path, method, etc... as well as the response code and body size. Here is an example:

1
{
2
"apiVersion": "core/v1",
3
"entry": {
4
"common": {
5
"connectionID": "muxv-ofat-eta1fervm8z5vairkw6jysiy-090t-nn63",
6
"endedAt": "2025-03-21T20:28:05.611245268Z",
7
"isAuthenticated": true,
8
"isAuthorized": true,
9
"namespaceRef": {
10
"apiVersion": "core/v1",
11
"kind": "Namespace",
12
"name": "default",
13
"resourceVersion": "0195b81d-43ec-7ba7-81f7-9b77202b3612",
14
"uid": "bff0768e-eec8-4044-a972-9f7af81b79d8"
15
},
16
"regionRef": {
17
"apiVersion": "core/v1",
18
"kind": "Region",
19
"name": "default",
20
"uid": "6e233d12-2fd6-4670-a82d-8c712e1f7374"
21
},
22
"sequence": "18",
23
"serviceRef": {
24
"apiVersion": "core/v1",
25
"kind": "Service",
26
"name": "essh1.default",
27
"resourceVersion": "0195ba63-6c77-7b4c-93f6-0da38b0d5107",
28
"uid": "b800371e-9521-42a0-8665-5ef1937a8a07"
29
},
30
"sessionID": "muxv-ofat-eta1fervm8z5vairkw6jysiy-090t-nn63-q7r1lr",
31
"sessionRef": {
32
"apiVersion": "core/v1",
33
"kind": "Session",
34
"name": "usr1-3pt93j",
35
"resourceVersion": "0195ba62-a468-7ca8-bf8b-2cd998c5ae23",
36
"uid": "e8bea7be-b858-4457-a8ee-a0cb7f6c91da"
37
},
38
"startedAt": "2025-03-21T20:28:01.580119846Z",
39
"userRef": {
40
"apiVersion": "core/v1",
41
"kind": "User",
42
"name": "usr1",
43
"resourceVersion": "0195b821-228d-79b2-92f3-4bb458944f97",
44
"uid": "e0f251b2-6952-4a46-a771-2712a45c47f5"
45
}
46
},
47
"info": {
48
"ssh": {
49
"sessionRecording": {
50
"data": "G1...aA==",
51
"type": "STDOUT"
52
},
53
"type": "SESSION_RECORDING"
54
}
55
}
56
},
57
"kind": "AccessLog",
58
"metadata": {
59
"actorRef": {
60
"apiVersion": "core/v1",
61
"kind": "Session",
62
"name": "usr1-3pt93j",
63
"resourceVersion": "0195ba62-a468-7ca8-bf8b-2cd998c5ae23",
64
"uid": "e8bea7be-b858-4457-a8ee-a0cb7f6c91da"
65
},
66
"createdAt": "2025-03-21T20:28:05.611236444Z",
67
"id": "vj3p-rbwf-wyz5tum1jtmyb394y7yoh8cn-yjlg-ilh1",
68
"targetRef": {
69
"apiVersion": "core/v1",
70
"kind": "Service",
71
"name": "essh1.default",
72
"resourceVersion": "0195ba63-6c77-7b4c-93f6-0da38b0d5107",
73
"uid": "b800371e-9521-42a0-8665-5ef1937a8a07"
74
}
75
}
76
}

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.

© 2025 octelium.comOctelium Labs, LLCAll rights reserved
Octelium and Octelium logo are trademarks of Octelium Labs, LLC.
WireGuard is a registered trademark of Jason A. Donenfeld