From b803d1c0b43cf6fddbc6b11c690f43ec4e649e84 Mon Sep 17 00:00:00 2001 From: David Galloway Date: Wed, 20 Oct 2021 15:34:54 -0400 Subject: [PATCH] dhcp-server: Enable support for dhcpd6 Signed-off-by: David Galloway --- roles/dhcp-server/README.rst | 34 ++++++--- roles/dhcp-server/defaults/main.yml | 2 + roles/dhcp-server/tasks/ipv4.yml | 72 +++++++++++++++++++ roles/dhcp-server/tasks/ipv6.yml | 26 +++++++ roles/dhcp-server/tasks/main.yml | 67 ++--------------- roles/dhcp-server/templates/dhcpd.conf.j2 | 2 + .../templates/dhcpd.subnet.conf.j2 | 14 ++-- roles/dhcp-server/templates/dhcpd6.conf.j2 | 11 +++ 8 files changed, 152 insertions(+), 76 deletions(-) create mode 100644 roles/dhcp-server/defaults/main.yml create mode 100644 roles/dhcp-server/tasks/ipv4.yml create mode 100644 roles/dhcp-server/tasks/ipv6.yml create mode 100644 roles/dhcp-server/templates/dhcpd6.conf.j2 diff --git a/roles/dhcp-server/README.rst b/roles/dhcp-server/README.rst index a3f67627..00f8ccc6 100644 --- a/roles/dhcp-server/README.rst +++ b/roles/dhcp-server/README.rst @@ -26,24 +26,25 @@ This role basically has two required and two optional variables: | - one-lease-per-client: "true" | | | | | +---------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ -|:: | This is large dictionary that gets parsed out into individual dhcpd config files. Each top-level key (``front`` and ``ipmi`` in the example) will get its own dhcp conf file created. The example shown to the left is our actual ``dhcp_subnets`` dictionary. | +|:: | This is a large dictionary that gets parsed out into individual dhcpd config files. Each top-level key (``front`` and ``ipmi`` in the example) will get its own dhcp conf file created. The example shown to the left is our actual ``dhcp_subnets`` dictionary. | | | | | dhcp_subnets: | | -| front: | Under each subnet, ``cidr``, ``ipvar``, and ``macvar`` are required. ``ipvar`` and ``macvar`` tell the Jinja2 template which IP address and MAC address should be used for each host in each subnet config file. | +| front: | Under each subnet, ``version``, ``cidr``, ``ipvar``, and ``macvar`` are required. ``ipvar`` and ``macvar`` tell the Jinja2 template which IP address and MAC address should be used for each host in each subnet config file. | +| version: '4' | | | cidr: 172.21.0.0/20 | | | ipvar: ip | Here's a line from our Ansible inventory host file | | macvar: mac | | -| domain_name: front.sepia.ceph.com | ``smithi001.front.sepia.ceph.com mac=0C:C4:7A:BD:15:E8 ip=172.21.15.1 ipmi=172.21.47.1 bmc=0C:C4:7A:6E:21:A7`` | +| domain_name: front.sepia.ceph.com | ``smithi001.front.sepia.ceph.com mac=0C:C4:7A:BD:15:E8 ip=172.21.15.1 ipmi=172.21.47.1 bmc=0C:C4:7A:6E:21:A7 ipv6=2620:52:3:3:ec4:7aff:febd:15e8`` | | domain_search: | | -| - front.sepia.ceph.com | This will result in a static IP entry for smithi001-front with IP 172.21.15.1 and MAC 0C:C4:7A:BD:15:E8 in ``dhcpd.front.conf`` and a smithi001-ipmi entry with IP 172.21.47.1 with MAC 0C:C4:7A:6E:21:A7 in ``dhcpd.ipmi.conf``. | +| - front.sepia.ceph.com | This will result in: | | - sepia.ceph.com | | -| domain_name_server: | The ``next_server`` and ``filename`` values can be overridden by ansible group or host. See below. | -| - 172.21.0.1 | | -| - 172.21.0.2 | All the other keys are optional. | +| domain_name_server: | - a static IP entry for smithi001-front with IP 172.21.15.1 and MAC 0C:C4:7A:BD:15:E8 in ``dhcpd.front.conf`` | +| - 172.21.0.1 | - a smithi001-ipmi entry with IP 172.21.47.1 with MAC 0C:C4:7A:6E:21:A7 in ``dhcpd.ipmi.conf`` | +| - 172.21.0.2 | - a smithi001-front-ipv6 entry with IP 2620:52:3:3:ec4:7aff:febd:15e8 with MAC 0C:C4:7A:BD:15:E8 in ``dhcpd.front-ipv6.conf`` | | routers: 172.21.15.254 | | -| next_server: 172.21.0.11 | | +| next_server: 172.21.0.11 | The ``next_server`` and ``filename`` values can be overridden by ansible group or host. See below. | | filename: "/pxelinux.0" | | -| classes: | | +| classes: | All the other keys are optional. | | virtual: "match if substring(hardware, 0, 4) = 01:52:54:00" | | | lxc: "match if substring(hardware, 0, 4) = 01:52:54:ff" | | | pools: | | @@ -58,6 +59,7 @@ This role basically has two required and two optional variables: | lxc: | | | range: 172.21.14.1 172.21.14.200 | | | ipmi: | | +| version: '4' | | | cidr: 172.21.32.0/20 | | | ipvar: ipmi | | | macvar: bmc | | @@ -76,6 +78,18 @@ This role basically has two required and two optional variables: | range: 172.21.43.1 172.21.43.100 | | | next_server: 172.21.0.11 | | | filename: "/pxelinux.0" | | +| front-ipv6: | | +| version: '6' | | +| cidr: 2620:52:3:3::/64 | | +| ipvar: ipv6 | | +| macvar: mac | | +| domain_name: front.sepia.ceph.com | | +| domain_search: | | +| - front.sepia.ceph.com | | +| - sepia.ceph.com | | +| routers: 2620:52:3:3:ffff:ffff:ffff:fffe/64 | | +| next_server: 2620:52:3:3:225:90ff:fee3:d94a | | +| filename: "/pxelinux.0" | | | | | +---------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | **Optional Variables** | @@ -96,6 +110,8 @@ This role basically has two required and two optional variables: +---------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``dhcp_option_hostname: False`` | Should this host get ``option host-name "{{ ansible_host }}";`` defined in its host declaration? Defaults to False. Override in secrets repo per host/group. | +---------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| ``enable_ipv6: False`` | Configure dhcpd6? Defaults to False. Override in secrets repo per host/group. Must set ``version: 6`` for IPv6 subnets in ``dhcp_subnets`` dictionary. | ++---------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ .. _docs: https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html#variable-precedence-where-should-i-put-a-variable .. _dhcpd: https://linux.die.net/man/8/dhcpd diff --git a/roles/dhcp-server/defaults/main.yml b/roles/dhcp-server/defaults/main.yml new file mode 100644 index 00000000..eb7f3ee5 --- /dev/null +++ b/roles/dhcp-server/defaults/main.yml @@ -0,0 +1,2 @@ +--- +enable_ipv6: false diff --git a/roles/dhcp-server/tasks/ipv4.yml b/roles/dhcp-server/tasks/ipv4.yml new file mode 100644 index 00000000..aa81643f --- /dev/null +++ b/roles/dhcp-server/tasks/ipv4.yml @@ -0,0 +1,72 @@ +--- +- name: Install/update packages + yum: + name: dhcp + state: latest + register: dhcp_yum_transaction + tags: packages + +- name: Check for firewalld + command: firewall-cmd --state + register: firewalld_state + ignore_errors: true + +- name: Check for iptables + command: systemctl status iptables + register: iptables_state + ignore_errors: true + +- name: Make sure firewalld is running + service: + name: firewalld + state: started + enabled: yes + when: + - not ansible_check_mode + - iptables_state.rc != 0 + - not (firewalld_state.msg is defined and "'No such file or directory' in firewalld_state.msg") + +- name: Configure firewalld + firewalld: + service: dhcp + state: enabled + permanent: true + immediate: yes + when: + - not ansible_check_mode + - iptables_state.rc != 0 + - not (firewalld_state.msg is defined and "'No such file or directory' in firewalld_state.msg") + +- name: Write global dhcpd.conf + template: + src: dhcpd.conf.j2 + dest: /etc/dhcp/dhcpd.conf + backup: yes + register: dhcp_global_config + +# NOTE: This will write the IPv6 configs too just to avoid iterating over `dhcp_subnets: {}` twice. +# ipv6.yml will configure and restart the dhcpd6 service +- name: Write each subnet config + template: + src: dhcpd.subnet.conf.j2 + dest: "/etc/dhcp/dhcpd.{{ item }}.conf" + backup: yes + with_items: "{{ dhcp_subnets }}" + register: dhcp_subnet_config + +- name: Test new config + command: dhcpd -t -cf /etc/dhcp/dhcpd.conf + register: dhcpd_config_test_result + when: + - not ansible_check_mode + - (dhcp_global_config is changed or dhcp_subnet_config is changed) + +- name: Restart dhcpd + service: + name: dhcpd + state: restarted + when: + - not ansible_check_mode + - (dhcp_global_config is changed or dhcp_subnet_config is changed) + - dhcpd_config_test_result is defined + - dhcpd_config_test_result.rc == 0 diff --git a/roles/dhcp-server/tasks/ipv6.yml b/roles/dhcp-server/tasks/ipv6.yml new file mode 100644 index 00000000..c31ee8cd --- /dev/null +++ b/roles/dhcp-server/tasks/ipv6.yml @@ -0,0 +1,26 @@ +--- +- name: Write global dhcpd6.conf + template: + src: dhcpd6.conf.j2 + dest: /etc/dhcp/dhcpd6.conf + backup: yes + register: dhcp6_global_config + +- name: Test new config + command: dhcpd -t -6 -cf /etc/dhcp/dhcpd6.conf + register: dhcpd6_config_test_result + when: + - not ansible_check_mode + - (dhcp6_global_config is changed or dhcp_subnet_config is changed) + +- name: Restart dhcpd6 + service: + name: dhcpd6 + state: restarted + when: + - not ansible_check_mode + - dhcp6_global_config is defined + - dhcp_subnet_config is defined + - (dhcp6_global_config is changed or dhcp_subnet_config is changed) + - dhcpd6_config_test_result is defined + - dhcpd6_config_test_result.rc == 0 diff --git a/roles/dhcp-server/tasks/main.yml b/roles/dhcp-server/tasks/main.yml index 97271654..4783f191 100644 --- a/roles/dhcp-server/tasks/main.yml +++ b/roles/dhcp-server/tasks/main.yml @@ -1,64 +1,7 @@ --- -- name: Install/update packages - yum: - name: dhcp - state: latest - register: dhcp_yum_transaction +- name: Run IPv4 DHCP server tasks + include_tasks: ipv4.yml -- name: Check for firewalld - command: firewall-cmd --state - register: firewalld_state - ignore_errors: true - -- name: Check for iptables - command: systemctl status iptables - register: iptables_state - ignore_errors: true - -- name: Make sure firewalld is running - service: - name: firewalld - state: started - enabled: yes - when: - - iptables_state.rc != 0 - - not (firewalld_state.msg is defined and "'No such file or directory' in firewalld_state.msg") - -- name: Configure firewalld - firewalld: - service: dhcp - state: enabled - permanent: true - immediate: yes - when: - - iptables_state.rc != 0 - - not (firewalld_state.msg is defined and "'No such file or directory' in firewalld_state.msg") - -- name: Write global dhcpd.conf - template: - src: dhcpd.conf.j2 - dest: /etc/dhcp/dhcpd.conf - backup: yes - register: dhcp_global_config - -- name: Write each subnet config - template: - src: dhcpd.subnet.conf.j2 - dest: "/etc/dhcp/dhcpd.{{ item }}.conf" - backup: yes - with_items: "{{ dhcp_subnets }}" - register: dhcp_subnet_config - -- name: Test new config - command: dhcpd -t -cf /etc/dhcp/dhcpd.conf - register: dhcpd_config_test_result - when: dhcp_global_config is changed or dhcp_subnet_config is changed - -- name: Restart dhcpd - service: - name: dhcpd - state: restarted - when: - - (dhcp_global_config is changed or dhcp_subnet_config is changed) - - dhcpd_config_test_result is defined - - dhcpd_config_test_result.rc == 0 +- name: Run IPv6 DHCP server tasks + include_tasks: ipv6.yml + when: enable_ipv6|bool diff --git a/roles/dhcp-server/templates/dhcpd.conf.j2 b/roles/dhcp-server/templates/dhcpd.conf.j2 index 5b0c2378..7d0af353 100644 --- a/roles/dhcp-server/templates/dhcpd.conf.j2 +++ b/roles/dhcp-server/templates/dhcpd.conf.j2 @@ -5,5 +5,7 @@ {% endfor %} {% for key, value in dhcp_subnets.items() %} +{% if value.version == "4" %} include "/etc/dhcp/dhcpd.{{ key }}.conf"; +{% endif %} {% endfor %} diff --git a/roles/dhcp-server/templates/dhcpd.subnet.conf.j2 b/roles/dhcp-server/templates/dhcpd.subnet.conf.j2 index 2d93039c..1d1a5c83 100644 --- a/roles/dhcp-server/templates/dhcpd.subnet.conf.j2 +++ b/roles/dhcp-server/templates/dhcpd.subnet.conf.j2 @@ -1,6 +1,10 @@ {% for subnet, subnet_item in dhcp_subnets.items() %} {% if subnet == item %} +{% if subnet_item.version == "6" %}{% set is_six = "6" %} +subnet6 {{ subnet_item.cidr }} { +{% else %}{% set is_six = "" %} subnet {{ subnet_item.cidr | ipaddr('network') }} netmask {{ subnet_item.cidr | ipaddr('netmask') }} { +{% endif %} {% if subnet_item.domain_name is defined -%} option domain-name "{{ subnet_item.domain_name }}"; {% endif -%} @@ -10,13 +14,13 @@ subnet {{ subnet_item.cidr | ipaddr('network') }} netmask {{ subnet_item.cidr | {% if subnet_item.domain_name_servers is defined -%} option domain-name-servers {{ subnet_item.domain_name_servers|join(', ') }}; {% endif -%} - {% if subnet_item.routers is defined -%} + {% if subnet_item.routers is defined and not subnet_item.version == "6" -%} option routers {{ subnet_item.routers }}; {% endif -%} - {% if subnet_item.next_server is defined -%} + {% if subnet_item.next_server is defined and not subnet_item.version == "6" -%} next-server {{ subnet_item.next_server }}; {% endif -%} - {% if subnet_item.filename is defined -%} + {% if subnet_item.filename is defined and not subnet_item.version == "6" -%} filename "{{ subnet_item.filename }}"; {% endif %} @@ -54,7 +58,7 @@ subnet {{ subnet_item.cidr | ipaddr('network') }} netmask {{ subnet_item.cidr | {%- endif -%} {% for host in groups['all'] | sort | unique -%} - {% if hostvars[host][subnet_item.macvar] is defined -%} + {% if hostvars[host][subnet_item.macvar] is defined and hostvars[host][subnet_item.ipvar] is defined -%} {% if hostvars[host][subnet_item.ipvar] | ipaddr(subnet_item.cidr) | ipaddr('bool') -%} host {{ host.split('.')[0] }}-{{ subnet }} { {% if hostvars[host]['dhcp_next_server'] is defined -%} @@ -65,7 +69,7 @@ subnet {{ subnet_item.cidr | ipaddr('network') }} netmask {{ subnet_item.cidr | option domain-name-servers {{ hostvars[host]['domain_name_servers']|join(', ') }}; {% endif -%} hardware ethernet {{ hostvars[host][subnet_item.macvar] }}; - fixed-address {{ hostvars[host][subnet_item.ipvar] }}; + fixed-address{{ is_six }} {{ hostvars[host][subnet_item.ipvar] }}; {% if hostvars[host]['dhcp_option_hostname'] is defined and hostvars[host]['dhcp_option_hostname'] == true %} option host-name "{{ host.split('.')[0] }}"; {% endif -%} diff --git a/roles/dhcp-server/templates/dhcpd6.conf.j2 b/roles/dhcp-server/templates/dhcpd6.conf.j2 new file mode 100644 index 00000000..5ea5fbd9 --- /dev/null +++ b/roles/dhcp-server/templates/dhcpd6.conf.j2 @@ -0,0 +1,11 @@ +{% for item in dhcp_global_options %} +{% for key, value in item.items() %} +{{ key }} {{ value }}; +{% endfor %} +{% endfor %} + +{% for key, value in dhcp_subnets.items() %} +{% if value.version == "6" %} +include "/etc/dhcp/dhcpd.{{ key }}.conf"; +{% endif %} +{% endfor %}