10

I am using Ansible and ufw to setup a firewall on my servers. As part of the ufw rules I would like to allow SSH from the Ansible control machine, but not from anywhere else. My question is - what is the best way to get the IP address of the control machine itself so I can put it into the rule?

I'm aware that I can use facts to get the IP address of the machine I am running the playbook on, but I don't see any easy way to get it automatically for the machine that is running ansible.

I'd like to avoid adding a new variable to represent this if possible since it would be nice if it was automatically discoverable, though if that's the only known best way to do it then I will just do that.

EDIT: I found this duplicate question which is the same as mine, however it also is unanswered so will leave this open for a bit.

Community
  • 1
  • 1
Cory
  • 22,772
  • 19
  • 94
  • 91

6 Answers6

9
{{ ansible_env['SSH_CLIENT'].split() | first }}

works, but you have to gather facts about connection variables from default user, so eighter:

  • Set «gather_facts: yes» and not «become: yes» on playbook level
  • More reliable: run «setup» task (without «become: yes» and before this «ansible_env» usage — better on «pre_tasks» section).

If you run «gather/setup» with «become», you will later get «One or more undefined variables: 'dict object' has no attribute 'SSH_CLIENT'» (this is becase sudoed «setup» can catch only small set of variables).

belonesox
  • 121
  • 1
  • 7
  • The point about running "gather / setup" with `become: no` is the key to getting this to work. Thanks! – RichVel Feb 02 '17 at 07:00
3

The easiset way is to add connection local

---
 - name: get my public IP
   ipify_facts: api_url=http://api.ipify.org
   connection: local

 - firewalld: rich_rule='rule family=ipv4 source address={{ ipify_public_ip }} accept' permanent=no state=enabled timeout=300
   become: true
Suire
  • 29
  • 3
1

I just hacked this solution and it worked. Is this something you are looking for?

- debug var="{{hostvars[inventory_hostname]['ansible_env']['SSH_CLIENT'].split(' ')[0]}}"
helloV
  • 50,176
  • 7
  • 137
  • 145
  • 1
    I think this will get the remote host's IP not the IP of the control machine. Did you see otherwise? – Cory Jan 01 '16 at 23:50
  • @Cory Your question was not clear and I misunderstood it. Updated the solution. See if it works. – helloV Jan 02 '16 at 00:21
  • 1
    This doesn't seem to work. I get the following error: `One or more undefined variables: 'dict object' has no attribute 'SSH_CLIENT'`. This is on Ansible 1.9. – Cory Jan 02 '16 at 16:04
  • @Cory can you post your play? – helloV Jan 02 '16 at 16:15
  • Here's the version that I tried with this syntax that was failing: https://gist.github.com/czue/6365bd98d9f35e395d9c – Cory Jan 03 '16 at 23:21
1

The "ipify_facts" method described in a different answer should work if:

  1. Your control host has only one interface, or if it has multiple interfaces and the interface used for reaching towards the ipify api_url is the same as for reaching towards your remote host, -and-
  2. Your control host has a public IP (no NAT used at all), or your control host is inside NAT but the remote host is outside.

If either of the above conditions is not met (that is, if your control host has multiple interfaces and/or your control host as well as your remote host are both behind NAT and using private addresses), then a different (albeit slightly more complex) way is to replicate the following in Ansible:

$ ip route get 203.0.113.4
203.0.113.4 via 172.26.232.1 dev enp0s3  src 172.26.232.125
                  cache
$

This will show you the interface and the source address (172.26.232.125) to be used for reaching towards a specific destination (203.0.113.4).

One way to implement this in Ansible would be:

---
- name: Obtain local IP address
  local_action: shell ip route get {{ inventory_hostname }} | awk '{ for (x=1;x<=NF;x++) if ($x=="src") print $(x+1) }'
  register: local_address
  changed_when: false

- name: Print my local address
  debug: msg="Source address towards {{ inventory_hostname }} is {{ local_address.stdout }}"

Output:

$ ansible-playbook test.yml  -i inventory.txt

PLAY [test] ********************************************************************

TASK [Obtain local IP address] *************************************************
ok: [203.0.113.4 -> localhost]

TASK [Print my local address] **************************************************
ok: [203.0.113.4] => {
    "msg": "Source address towards 203.0.113.4 is 172.26.232.125"
}

This should also work if you have multiple remote hosts reachable via different interfaces.

An additional step in the "shell" action would be needed if your inventory_hostname was a DNS hostname as opposed to a numerical IP address.

C. Koester
  • 11
  • 1
0
{{ ansible_env['SSH_CLIENT'].split()[0] }}
Arbab Nazar
  • 22,378
  • 10
  • 76
  • 82
udondan
  • 57,263
  • 20
  • 190
  • 175
  • 1
    This doesn't seem to work. I get the following error: `One or more undefined variables: 'dict object' has no attribute 'SSH_CLIENT'`. This is on Ansible 1.9. – Cory Jan 02 '16 at 16:04
  • Is the task running on a remote host or locally? If remotely, what transport are you using? `SSH_CLIENT` should be contained when you use OpenSSH. – udondan Jan 02 '16 at 16:37
  • 1
    It's running on a remote host and is using SSH I believe (how would I confirm that?). Possibly I'm running into [this issue](https://github.com/ansible/ansible/issues/9260)? – Cory Jan 03 '16 at 23:19
  • The bug report seems to be limited to templates? Can you try output it in a debug task like so?: `- debug: message="{{ ansible_env['SSH_CLIENT'].split()[0] }}"`- Though, me and my team are using it this way in a template since 1.6 until the 1.9.4. – udondan Jan 04 '16 at 02:11
0
{{ ansible_env['SSH_CLIENT'].split() | first }}
Arbab Nazar
  • 22,378
  • 10
  • 76
  • 82