[PR #592] feat: implement proxy jump for egress connections #596

Open
opened 2026-05-07 00:21:41 +02:00 by BreizhHardware · 0 comments

📋 Pull Request Information

Original PR: https://github.com/ovh/the-bastion/pull/592
Author: @jon4hz
Created: 9/26/2025
Status: 🔄 Open

Base: masterHead: feat-proxyjump


📝 Commits (10+)

  • 95f9117 feat: implement proxy jump for egress connections
  • aabbbf9 feat(osh): parse proxyjump options correctly
  • 030b334 feat(scp): support proxyjump
  • e710b00 chore: adjust machine display to include proxy info
  • 157a7a1 chore: run perl tidy
  • 1868261 fix: handle access check correctly with proxy options
  • 9da71bd fix: reset proxy connection env var
  • f79d4a0 fix: handle proxy connection in access test
  • df6791b fix: return proxyIP and proxyPort in json output
  • 11df17b fix: correct proxy parameter in groupAddServer helper

📊 Changes

36 files changed (+2466 additions, -423 deletions)

View changed files

📝 bin/helper/osh-accountAddGroupServer (+34 -20)
📝 bin/helper/osh-accountModifyPersonalAccess (+36 -9)
📝 bin/helper/osh-groupAddServer (+30 -3)
📝 bin/plugin/group-aclkeeper/groupAddServer (+60 -33)
📝 bin/plugin/group-aclkeeper/groupAddServer.json (+16 -8)
📝 bin/plugin/group-aclkeeper/groupDelServer (+42 -18)
📝 bin/plugin/group-aclkeeper/groupDelServer.json (+12 -5)
📝 bin/plugin/group-gatekeeper/groupAddGuestAccess (+61 -37)
📝 bin/plugin/group-gatekeeper/groupAddGuestAccess.json (+9 -1)
📝 bin/plugin/group-gatekeeper/groupDelGuestAccess (+55 -31)
📝 bin/plugin/group-gatekeeper/groupDelGuestAccess.json (+7 -1)
📝 bin/plugin/open/scp (+90 -10)
📝 bin/plugin/open/selfListSessions (+29 -13)
📝 bin/plugin/restricted/accountAddPersonalAccess (+45 -10)
📝 bin/plugin/restricted/accountAddPersonalAccess.json (+11 -11)
📝 bin/plugin/restricted/accountDelPersonalAccess (+42 -18)
📝 bin/plugin/restricted/accountDelPersonalAccess.json (+7 -1)
📝 bin/plugin/restricted/selfAddPersonalAccess (+50 -11)
📝 bin/plugin/restricted/selfAddPersonalAccess.json (+11 -11)
📝 bin/plugin/restricted/selfDelPersonalAccess (+41 -17)

...and 16 more files

📄 Description

Hi there,

Since proxy jumping is an important feature for me, I started to give it a shot at implementing it myself, but I'm a bit stuck and could require some inputs.

My idea was to add --proxy-host and --proxy-port flag when adding a new server. For simplicity, the --proxy-port must be explicit, so there is no flag like --proxy-port-any.

If someone adds a server with a proxy, the proxy information gets stored in the comments of the acl rules. I updated the allowkeeper.inc library to parse those parameters accordingly - and allowdeny.inc was adjusted to validate those parameters when checking if access to a server should be granted.

The acl validation works well so far, however I'm struggling with the required changes in the osh.pl script.
When I try to add a new access, the ssh_test_access_way function starts the ssh test command with a new ProxyCommand option.
My theory is that the proxy command causes ssh to start a subprocess, this ssh subprocess gets then interpreted by the osh.pl session. This causes a race condition, because osh.pl is supposed to do acl checks, but the acl for that new server isn't written yet since this is the connection to dermine if the server should get added to the acls.

The command from the ssh subprocess also doesn't get interpreted correctly (because it shouldn't be handled by osh.pl in the first place, imo)

For example, when I execute: selfAddPersonalAccess --host 192.168.1.10 --port 22 --user jonah --proxy-host 192.168.1.20 --proxy-port 22, I get the following log:

~ <1211651:/opt/bastion/bin/shell/osh.pl> remainingOptions <exec/ssh -o ConnectTimeout=5 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o PreferredAuthentications=publickey -i /home/jonah/.ssh/id_ecdsa521_private.1757717930 -p 22 -W 192.168.1.10:22 -l jonah 192.168.1.20>
~ <1211651:/opt/bastion/bin/shell/osh.pl> Remaining options exec/ssh -o ConnectTimeout=5 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o PreferredAuthentications=publickey -i /home/jonah/.ssh/id_ecdsa521_private.1757717930 -p 22 -W 192.168.1.10:22 -l jonah 192.168.1.20
~ <1211651:/opt/bastion/bin/shell/osh.pl> After shift, remainingOptions ssh -o ConnectTimeout=5 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o PreferredAuthentications=publickey -i /home/jonah/.ssh/id_ecdsa521_private.1757717930 -p 22 -W 192.168.1.10:22 -l jonah 192.168.1.20
~ <1211651:/opt/bastion/bin/shell/osh.pl> host = exec
~ <1211651:/opt/bastion/bin/shell/osh.pl> user  host exec
~ <1211651:/opt/bastion/bin/shell/osh.pl> Going to add extra command ssh -o ConnectTimeout=5 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o PreferredAuthentications=publickey -i /home/jonah/.ssh/id_ecdsa521_private.1757717930 -p 22 -W 192.168.1.10:22 -l jonah 192.168.1.20
~ <1211651:/opt/bastion/bin/shell/osh.pl> checking if 'exec' is already an IP (v4=1 v6=0)
~ <1211651:/opt/bastion/bin/shell/osh.pl> Trying to resolve 'exec' because is_valid_ip() says it's not an IP

This is the command that ssh_test_access_way executes:

ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o PreferredAuthentications=publickey \
	-i /home/jonah/.ssh/id_ecdsa521_private.1757719227 \
	-p 22 -l jonah 192.168.1.10 \
	-o ProxyCommand="ssh -o ConnectTimeout=5 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o PreferredAuthentications=publickey -i /home/jonah/.ssh/id_ecdsa521_private.1757719227 -p 22 -l jonah -W %h:%p 192.168.1.20" -T -- true
root@bastion:~# echo $?
0
root@bastion:~# 

As you can see, this work when executed outside the osh.pl environment.

If I enable ssh debugging, I get the following log:

debug1: OpenSSH_10.0p2 Debian-7, OpenSSL 3.5.1 1 Jul 2025
debug1: Reading configuration data /etc/ssh/ssh_config
debug1: Executing proxy command: exec ssh -o ConnectTimeout=5 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o PreferredAuthentications=publickey -i /home/jonah/.ssh/id_ecdsa521_private.1757719227 -p 22 -l jonah -W 192.168.1.10:22 192.168.1.20

That's where my theory of the subprocess spawned by ssh comes from.

I hope this description is somewhat understandable. It would be super nice if you could take a look at this and give some inputs, if you have an idea how to work around this issue.


When a using the proxy feature, the following assumptions are made:

  • the proxy host uses the same username and supports the same authentication as the target host

Limitations with the current implementation:

  • only the ssh protocol is supported, not scp, sftp or rsync
  • only supported by scp, not sftp or rsync (at least for now)
  • logging to the sqlite db isn't implemented (yet). This requires a schema change, and I'm not sure how to implement that best
  • Not supported by guest access
  • the naming of the ttyrec files should probably be adjusted to include the proxy IP
  • the proxy host can't be subnet, it must be a hostname or an IP

closes #591 and #250


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/ovh/the-bastion/pull/592 **Author:** [@jon4hz](https://github.com/jon4hz) **Created:** 9/26/2025 **Status:** 🔄 Open **Base:** `master` ← **Head:** `feat-proxyjump` --- ### 📝 Commits (10+) - [`95f9117`](https://github.com/ovh/the-bastion/commit/95f9117394603b984bd09aba03a84f7e011b241d) feat: implement proxy jump for egress connections - [`aabbbf9`](https://github.com/ovh/the-bastion/commit/aabbbf959a0af8f542c760cdc00b52a7ac86c510) feat(osh): parse proxyjump options correctly - [`030b334`](https://github.com/ovh/the-bastion/commit/030b334b20192d0b1d670d3f4fe0161f7ae28eff) feat(scp): support proxyjump - [`e710b00`](https://github.com/ovh/the-bastion/commit/e710b006adaf723e40839c5352d97ad826e38682) chore: adjust machine display to include proxy info - [`157a7a1`](https://github.com/ovh/the-bastion/commit/157a7a18825c7c2be555022ef718f71bd10efe41) chore: run perl tidy - [`1868261`](https://github.com/ovh/the-bastion/commit/186826112940c20bf0c1497dd5aa6990ef37a10e) fix: handle access check correctly with proxy options - [`9da71bd`](https://github.com/ovh/the-bastion/commit/9da71bd46e497fff4475fc9dedc36c97397afd64) fix: reset proxy connection env var - [`f79d4a0`](https://github.com/ovh/the-bastion/commit/f79d4a03d5aa019be395cbb2871db281b8bd55d8) fix: handle proxy connection in access test - [`df6791b`](https://github.com/ovh/the-bastion/commit/df6791bdc94a143e63f4bf46ab1a50f21a8832cb) fix: return proxyIP and proxyPort in json output - [`11df17b`](https://github.com/ovh/the-bastion/commit/11df17b62467add6cc6f05183e6cab1621bad903) fix: correct proxy parameter in groupAddServer helper ### 📊 Changes **36 files changed** (+2466 additions, -423 deletions) <details> <summary>View changed files</summary> 📝 `bin/helper/osh-accountAddGroupServer` (+34 -20) 📝 `bin/helper/osh-accountModifyPersonalAccess` (+36 -9) 📝 `bin/helper/osh-groupAddServer` (+30 -3) 📝 `bin/plugin/group-aclkeeper/groupAddServer` (+60 -33) 📝 `bin/plugin/group-aclkeeper/groupAddServer.json` (+16 -8) 📝 `bin/plugin/group-aclkeeper/groupDelServer` (+42 -18) 📝 `bin/plugin/group-aclkeeper/groupDelServer.json` (+12 -5) 📝 `bin/plugin/group-gatekeeper/groupAddGuestAccess` (+61 -37) 📝 `bin/plugin/group-gatekeeper/groupAddGuestAccess.json` (+9 -1) 📝 `bin/plugin/group-gatekeeper/groupDelGuestAccess` (+55 -31) 📝 `bin/plugin/group-gatekeeper/groupDelGuestAccess.json` (+7 -1) 📝 `bin/plugin/open/scp` (+90 -10) 📝 `bin/plugin/open/selfListSessions` (+29 -13) 📝 `bin/plugin/restricted/accountAddPersonalAccess` (+45 -10) 📝 `bin/plugin/restricted/accountAddPersonalAccess.json` (+11 -11) 📝 `bin/plugin/restricted/accountDelPersonalAccess` (+42 -18) 📝 `bin/plugin/restricted/accountDelPersonalAccess.json` (+7 -1) 📝 `bin/plugin/restricted/selfAddPersonalAccess` (+50 -11) 📝 `bin/plugin/restricted/selfAddPersonalAccess.json` (+11 -11) 📝 `bin/plugin/restricted/selfDelPersonalAccess` (+41 -17) _...and 16 more files_ </details> ### 📄 Description Hi there, Since proxy jumping is an important feature for me, I started to give it a shot at implementing it myself, but I'm a bit stuck and could require some inputs. My idea was to add `--proxy-host` and `--proxy-port` flag when adding a new server. For simplicity, the `--proxy-port` must be explicit, so there is no flag like `--proxy-port-any`. If someone adds a server with a proxy, the proxy information gets stored in the comments of the acl rules. I updated the `allowkeeper.inc` library to parse those parameters accordingly - and `allowdeny.inc` was adjusted to validate those parameters when checking if access to a server should be granted. The acl validation works well so far, however I'm struggling with the required changes in the `osh.pl` script. When I try to add a new access, the `ssh_test_access_way` function starts the ssh test command with a new `ProxyCommand` option. My theory is that the proxy command causes ssh to start a subprocess, this ssh subprocess gets then interpreted by the `osh.pl` session. This causes a race condition, because osh.pl is supposed to do acl checks, but the acl for that new server isn't written yet since this is the connection to dermine if the server should get added to the acls. The command from the ssh subprocess also doesn't get interpreted correctly (because it shouldn't be handled by `osh.pl` in the first place, imo) For example, when I execute: `selfAddPersonalAccess --host 192.168.1.10 --port 22 --user jonah --proxy-host 192.168.1.20 --proxy-port 22`, I get the following log: ``` ~ <1211651:/opt/bastion/bin/shell/osh.pl> remainingOptions <exec/ssh -o ConnectTimeout=5 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o PreferredAuthentications=publickey -i /home/jonah/.ssh/id_ecdsa521_private.1757717930 -p 22 -W 192.168.1.10:22 -l jonah 192.168.1.20> ~ <1211651:/opt/bastion/bin/shell/osh.pl> Remaining options exec/ssh -o ConnectTimeout=5 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o PreferredAuthentications=publickey -i /home/jonah/.ssh/id_ecdsa521_private.1757717930 -p 22 -W 192.168.1.10:22 -l jonah 192.168.1.20 ~ <1211651:/opt/bastion/bin/shell/osh.pl> After shift, remainingOptions ssh -o ConnectTimeout=5 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o PreferredAuthentications=publickey -i /home/jonah/.ssh/id_ecdsa521_private.1757717930 -p 22 -W 192.168.1.10:22 -l jonah 192.168.1.20 ~ <1211651:/opt/bastion/bin/shell/osh.pl> host = exec ~ <1211651:/opt/bastion/bin/shell/osh.pl> user host exec ~ <1211651:/opt/bastion/bin/shell/osh.pl> Going to add extra command ssh -o ConnectTimeout=5 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o PreferredAuthentications=publickey -i /home/jonah/.ssh/id_ecdsa521_private.1757717930 -p 22 -W 192.168.1.10:22 -l jonah 192.168.1.20 ~ <1211651:/opt/bastion/bin/shell/osh.pl> checking if 'exec' is already an IP (v4=1 v6=0) ~ <1211651:/opt/bastion/bin/shell/osh.pl> Trying to resolve 'exec' because is_valid_ip() says it's not an IP ``` This is the command that `ssh_test_access_way` executes: ``` ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o PreferredAuthentications=publickey \ -i /home/jonah/.ssh/id_ecdsa521_private.1757719227 \ -p 22 -l jonah 192.168.1.10 \ -o ProxyCommand="ssh -o ConnectTimeout=5 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o PreferredAuthentications=publickey -i /home/jonah/.ssh/id_ecdsa521_private.1757719227 -p 22 -l jonah -W %h:%p 192.168.1.20" -T -- true root@bastion:~# echo $? 0 root@bastion:~# ``` As you can see, this work when executed outside the `osh.pl` environment. If I enable ssh debugging, I get the following log: ``` debug1: OpenSSH_10.0p2 Debian-7, OpenSSL 3.5.1 1 Jul 2025 debug1: Reading configuration data /etc/ssh/ssh_config debug1: Executing proxy command: exec ssh -o ConnectTimeout=5 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o PreferredAuthentications=publickey -i /home/jonah/.ssh/id_ecdsa521_private.1757719227 -p 22 -l jonah -W 192.168.1.10:22 192.168.1.20 ``` That's where my theory of the subprocess spawned by ssh comes from. I hope this description is somewhat understandable. It would be super nice if you could take a look at this and give some inputs, if you have an idea how to work around this issue. --- When a using the proxy feature, the following assumptions are made: - ~the proxy host uses the same username and supports the same authentication as the target host~ Limitations with the current implementation: - ~only the ssh protocol is supported, not scp, sftp or rsync~ - only supported by scp, not sftp or rsync (at least for now) - ~logging to the sqlite db isn't implemented (yet). This requires a schema change, and I'm not sure how to implement that best~ - ~Not supported by guest access~ - ~the naming of the ttyrec files should probably be adjusted to include the proxy IP~ - the proxy host can't be subnet, it must be a hostname or an IP closes #591 and #250 --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/the-bastion#596
No description provided.