OpenSSH server has separate configuration variables for IP addresses to listen on and ports to listen on, for example in /etc/ssh/sshd_config:
ListenAddress 192.168.1.2
ListenAddress 127.0.0.1
Port 22
Port 53
Port 443
A SSH role I have written and I’m updating has variables for ssh_ports and ssh_listen_addresses defined like this:
ssh_listen_addresses:
type: list
elements: str
required: false
description: ListenAddress, IP addresses that SSH should listen for connections on.
ssh_ports:
type: list
elements: int
required: false
description: Port, a list of ports for SSH to listen on.
So for this example:
ssh_listen_addresses:
- 127.0.0.1
- 192.168.1.2
ssh_ports:
- 22
- 53
- 443
You can get the current SSHD configuration using sshd -T but this combines the two configuration variables:
sshd -T | grep ^listen
listenaddress 127.0.0.1:22
listenaddress 127.0.0.1:53
listenaddress 127.0.0.1:443
listenaddress 192.168.1.2:22
listenaddress 192.168.1.2:53
listenaddress 192.168.1.2:443
And you can parse this using jc, for example:
sshd -T | sort | jc -- sshd-conf -p -y | grep -A6 listenaddress
listenaddress:
- 127.0.0.1:22
- 127.0.0.1:53
- 127.0.0.1:443
- 192.168.1.2:22
- 192.168.1.2:53
- 192.168.1.2:443
Using Ansible:
- name: Run sshd -T to get the current configuration
ansible.builtin.command:
cmd: /usr/sbin/sshd -T
check_mode: false
changed_when: false
register: ssh_t
- name: Set a fact for the current SSHD configuration
ansible.builtin.set_fact:
ssh_cnf: "{{ ssh_t.stdout | community.general.jc('sshd_conf') }}"
I want to check that the Ansible configuration for addresses and ports matches the configuration that the service has, if it does then some tasks can be skipped, in order to test this I need to combine the ssh_listen_addresses and the ssh_ports list, so far I have this in vars/main.yml:
# Generate a list of addresses and ports that match the sshd -T format of listenaddress
# in a not very elegant way...
ssh_listen_addr_port: |-
{%- for ssh_listen_address in ssh_listen_addresses %}
{%- for ssh_port in ssh_ports %}
{{ ssh_listen_address }}:{{ ssh_port }}
{% endfor -%}
{% endfor -%}
ssh_listen_addr_port_list: >-
{{- ssh_listen_addr_port |
ansible.builtin.split('\n') |
ansible.builtin.reject('regex', '^$') -}}
Tasks can be skipped when this condition is false:
when: >-
(ssh_cnf is not defined) or
((ssh_cnf is defined) and
(ssh_cnf.listenaddress | ansible.builtin.difference(ssh_listen_addr_port_list) != []))
This works, but as you can see it looks fairly horrible, I realise that I could use set_fact with a loop to generate the ssh_listen_addr_port_list list but that also wouldn’t be very elegant, what am I missing, how can this be done more elegantly?