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 ~/.ssh/config
file:
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.
Comments
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:
--8<------
Host *.example.com !bastion.example.com
ProxyJump bastion.example.com
--8<------
The -W argument can be applied too if using ProxyCommand instead of ProxyJump:
--8<------
Host *.example.com !bastion.example.com
ProxyCommand ssh bastion.example.com -W %h:%p
--8<------
The issue with changing .ssh/config is that it's not easily sharable so does not work well for teams IMO.
We keep ansible.cfg inside git and we can use the ansible ssh options.
Putting the ssh options under ~/.ssh/config would mean we have to inform everyone to do this.
Keeping thins in ansible + git means that it works for everyone.
Have you thought about using the newer proxyjump option?
ansible_ssh_common_args='-o ProxyCommand="ssh -p 2222 -J [email protected]"'
I'm unable to use the -J option
The same effect can be achieved by incorporating ProxyCommand or ProxyJump in your
.ssh/config
file instead of adding this to Ansible inventory. In particular and assuming the above inventory, one could add the following to their.ssh/config
Therefore, when connects directly to
private-server-1.example.com
via SSH,ssh
will first make an SSH connection tobastion.example.com
and then use this, to connect toprivate-server-1.example.com
(establish TCP forwarding behind the scenes etc.). If you use Password Authentication on bothbastion.example.com
andprivate-server-1.example.com
you 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
paramiko
to connect.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_connection]
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 :
Host bastion
hostname [my bastion]
User [my user]
port 22
IdentityFile /home/[my user]/.ssh/[private key]
Host *
ProxyJump bastion
or
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?