Since I've set this up a number of times, but I just realized I've never documented it on my blog, I thought I'd finally do that.
I have a set of servers that are running on a private network. That network is connected to the Internet through a single reverse proxy / 'bastion' host.
But I still want to be able to manage the servers on the private network behind the bastion from outside.
Method 1 - Inventory vars
The first way to do it with Ansible is to describe how to connect through the proxy server in Ansible's inventory. This is helpful for a project that might be run from various workstations or servers without the same SSH configuration (the configuration is stored alongside the playbook, in the inventory).
In my Ansible project, I had an inventory file like the following:
[proxy] bastion.example.com [nodes] private-server-1.example.com private-server-2.example.com private-server-3.example.com
If I am connected to the private network directly, I can just run
ansible commands and playbooks, and Ansible can see all the servers and connect to them (assuming my SSH config is otherwise correct).
From the outside, though, I need to modify my inventory to look like the following:
[proxy] bastion.example.com [nodes] private-server-1.example.com private-server-2.example.com private-server-3.example.com [nodes:vars] ansible_ssh_common_args='-o ProxyCommand="ssh -p 2222 -W %h:%p -q [email protected]"'
This sets up an SSH proxy through bastion.example.com on port 2222 (if using the default port, 22, you can drop the port argument). The
-W argument tells SSH it can forward stdin and stdout through the host and port, effectively allowing Ansible to manage the node behind the bastion/jump server.
Method 2 - SSH config
The alternative, which would apply the proxy configuration to all SSH connections on a given workstation, is to add the following configuration inside your
Host bastion User username Hostname bastion.example.com Host private-server-*.example.com ProxyJump bastion
Ansible will automatically use whatever SSH options are defined in the user or global SSH config, so it should pick these settings up even if you don't modify your inventory.
This method is most helpful if you know your playbook will always be run from a server or workstation where the SSH config is present.
There's a "-J jumphost" option to ssh for that these days.
You can use this command as well: ansible_ssh_common_args: "-p 2222 -J [email protected]"
I configured the bastion host in the .ssh/config where it works not only for ansible:
Host *.example.com !bastion.example.com
The -W argument can be applied too if using ProxyCommand instead of ProxyJump:
Host *.example.com !bastion.example.com
ProxyCommand ssh bastion.example.com -W %h:%p
Have you thought about using the newer proxyjump option?
ansible_ssh_common_args='-o ProxyCommand="ssh -p 2222 -J [email protected]"'
The same effect can be achieved by incorporating ProxyCommand or ProxyJump in your
.ssh/configfile instead of adding this to Ansible inventory. In particular and assuming the above inventory, one could add the following to their
Therefore, when connects directly to
sshwill first make an SSH connection to
bastion.example.comand then use this, to connect to
private-server-1.example.com(establish TCP forwarding behind the scenes etc.). If you use Password Authentication on both
private-server-1.example.comyou will be requested to fill password two times. You can even use ProxyJump multiple times out of the box!
This is really handy, as your inventory is clean from unnecessary vars (e.g in cases you run Ansible both locally and from Jenkins inside private network) and can be used by Ansible out-of-the box. The only prerequisite is for Ansible to use native OpenSSH client (default) instead of
Thanks! I actually went ahead and edited the post to show both alternatives, because there are times when putting the config in the inventory is the best option, but I think many people would prefer the 2nd approach since it can help with other SSH interaction as well, assuming they're doing it from their own workstation.
Nice. However I prefer an alternative approach: Which would be to have an ansible.cfg with
ssh_args = -F ./ssh.cfg
And to make use of that ssh.cfg to use the, IMHO, much cleaner ProxyJump jump command. This has a secondary advantage that you can then pass it to ssh itself to debug say a complex jump through multiple jump boxes via `ssh -F ssh.cfg my-far-away-node`. A trivial example here for example: https://github.com/tonykay/dojo-ansible-container-toolkit/blob/main/lab…
It would be easy for example to use different keys and users for each of those 3 hosts and easy to debug.
ii uses .ssh/config for all this stuff, there is ProxyJump option for easier config
What about if it is possible to reach the target device through 2 or multiple bastions ? Thanks for help.
Same, just use the SSH config option and let ssh handle the dependency, so if A require B and B require C (A<-B<-C) ssh will recursively connect to the next ProxyJump node.
I use the inventory approach...however the "username" inside my inventory can vary.
Is there a way to work with a variable here like the current shell user passes his name through this variable ???
thanks for your posts.
I need to install ansible in my company through a bastion.
When i run :
ssh -t -A [my user]@[my bastion] [my user]@[my host]
I reach my host
But when i confugre my file .ssh/config like this :
hostname [my bastion]
User [my user]
IdentityFile /home/[my user]/.ssh/[private key]
ProxyCommand ssh [my user]@[my bastion] -W %h:%p
That doesn't work . I have this error :
|THIS IS A PRIVATE COMPUTER SYSTEM, UNAUTHORIZED ACCESS IS STRICTLY PROHIBITED.|
|ALL CONNECTIONS ARE LOGGED. IF YOU ARE NOT AUTHORIZED, DISCONNECT NOW. |
channel 0: open failed: administratively prohibited: open failed
stdio forwarding failed
kex_exchange_identification: Connection closed by remote host
Connection closed by UNKNOWN port 65535
Could you help me, please?