Commit e75df26e authored by Matthew Smith's avatar Matthew Smith
Browse files

Initial commit

parents
Copyright (c) 2013, Philippe Dellaert
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. All advertising materials mentioning features or use of this software
must display the following acknowledgement:
This product includes software developed by Philippe Dellaert.
4. Neither the name of Philippe Dellaert nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY PHILIPPE DELLAERT ''AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PHILIPPE DELLAERT BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
dhcp_server
===========
This role installs and configures a DHCP server.
Requirements
------------
This role requires Ansible 1.4 or higher and platform requirements are listed in the metadata file.
Ubuntu AppArmor
---------------
Since Ubuntu 14.04, AppArmor is configured to not allow dhcpd to access files outside a certain list of paths.
This prevents Ansible from running the check command on the template. The check is used to validate the correctness of the config file generated.
To prevent this, you can either disable AppArmor, manually configure it in such a way that it allows access to `/root/.ansible/tmp` for dhcpd or you can let this role do that for you:
If you specify the `configure_apparmor: true` variable for your host. This role will overwrite the `/etc/apparmor.d/local/usr.bin.dhcpd` file and specifically allow read-only access to `/root/.ansible/tmp`. It will first check if this file exists, if it does not, it will not do anything.
Difference between global and subnet interface options
-------------------------------------------------------
Global dhcp_interfaces option makes listen on defined interfaces all subnets. Interface per subnet definition allows listen as much subnets as you want.
Global dhcp_interfaces option does not work on systemd distros (ArchLinux, CentOS 7, Fedora), listen by default on interface with declared subnet. You cat rewrite systemd service, but is dirty. Instead this, describe interfaces in configuration. Is modern and properly.
Role Variables
--------------
The variables that can be passed to this role and a brief description about
them are as follows. These are all based on the configuration variables of the
DHCP server configuration.
# AppArmor configuration - important for Ubuntu 14.04
configure_apparmor: true
# Basic configuration information
dhcp_use_ansible_managed: true|false (default is true)
dhcp_interfaces: eth0
dhcp_common_domain: example.org
dhcp_common_nameservers: ns1.example.org, ns2.example.org
dhcp_common_default_lease_time: 600
dhcp_common_max_lease_time: 7200
dhcp_common_ddns_update_style: none
dhcp_common_authoritative: true
dhcp_common_log_facility: local7
dhcp_common_options:
- opt66 code 66 = string
dhcp_common_parameters:
- filename "pxelinux.0"
dhcp_common_unknown_clients: true|false (default is true)
# DDNS configuration
dhcp_ddns_client_updates: true|false (default is false)
dhcp_ddns_updates: true|false (default is true)
dhcp_ddns_update_static_leases: true|false (default is false)
dhcp_ddns_update_style: interim
dhcp_ddns_keys:
- the_key_name: the_key_value
dhcp_ddns_zones:
-
name:example.org
primary: 192.168.0.1
key: a_key_name_from_dhcp_ddns_keys_list
# Subnet configuration
dhcp_subnets:
# Required variables example
- base: 192.168.1.0
netmask: 255.255.255.0
# Full list of possibilities
- base: 192.168.10.0
netmask: 255.255.255.0
interface: vlan100
range_start: 192.168.10.150
range_end: 192.168.10.200
routers: 192.168.10.1
broadcast_address: 192.168.10.255
domain_nameservers: 192.168.10.1, 192.168.10.2
netbios_nameserver: 192.168.10.1
boot_server: "http://172.22.0.1:84"
domain_name: example.org
ntp_servers: pool.ntp.org
default_lease_time: 3600
max_lease_time: 7200
pools:
- range_start: 192.168.100.10
range_end: 192.168.100.20
rule: 'allow members of "foo"'
parameters:
- filename "pxelinux.0"
- range_start: 192.168.110.10
range_end: 192.168.110.20
rule: 'deny members of "foo"'
parameters:
- filename "pxelinux.0"
# Fixed lease configuration
dhcp_hosts:
- name: local-server
mac_address: "00:11:22:33:44:55"
fixed_address: 192.168.10.10
default_lease_time: 43200
max_lease_time: 86400
parameters:
- filename "pxelinux.0"
# Class configuration
dhcp_classes:
- name: foo
rule: 'match if substring (option vendor-class-identifier, 0, 4) = "SUNW"'
- name: CiscoSPA
rule: 'match if (( substring (option vendor-class-identifier,0,13) = "Cisco SPA504G" ) or
( substring (option vendor-class-identifier,0,12) = "Cisco SPA303" ))'
options:
- opt: 'opt66 "http://distrib.local/cisco.php?mac=$MAU"'
- opt: 'time-offset 21600'
# Shared network configurations
dhcp_shared_networks:
- name: shared-net
interface: vlan100
subnets:
- base: 192.168.100.0
netmask: 255.255.255.0
routers: 192.168.10.1
parameters:
- filename "pxelinux.0"
pools:
- range_start: 192.168.100.10
range_end: 192.168.100.20
rule: 'allow members of "foo"'
parameters:
- filename "pxelinux.0"
- range_start: 192.168.110.10
range_end: 192.168.110.20
rule: 'deny members of "foo"'
# Custom if else clause
dhcp_ifelse:
- condition: 'exists user-class and option user-class = "iPXE"'
val: 'filename "http://my.web.server/real_boot_script.php";'
else:
- val: 'filename "pxeboot.0";'
- val: 'filename "pxeboot.1";'
Examples
========
1) Install DHCP server on interface eth0 with one simple subnet:
- hosts: all
roles:
- role: dhcp_server
dhcp_interfaces: eth0
dhcp_common_domain: example.org
dhcp_common_nameservers: ns1.example.org, ns2.example.org
dhcp_common_default_lease_time: 600
dhcp_common_max_lease_time: 7200
dhcp_common_ddns_update_style: none
dhcp_common_authoritative: true
dhcp_common_log_facility: local7
dhcp_subnets:
- base: 192.168.10.0
netmask: 255.255.255.0
range_start: 192.168.10.150
range_end: 192.168.10.200
routers: 192.168.10.1
2) Install DHCP server with subnet per interface:
- hosts: all
roles:
- role: dhcp_server
dhcp_common_domain: example.org
dhcp_common_nameservers: ns1.example.org, ns2.example.org
dhcp_common_default_lease_time: 600
dhcp_common_max_lease_time: 7200
dhcp_common_ddns_update_style: none
dhcp_common_authoritative: true
dhcp_common_log_facility: local7
dhcp_subnets:
- base: 192.168.10.0
netmask: 255.255.255.0
interface: vlan10
range_start: 192.168.10.150
range_end: 192.168.10.200
routers: 192.168.10.1
- base: 192.168.20.0
netmask: 255.255.255.0
interface: vlan20
range_start: 192.168.20.150
range_end: 192.168.20.200
routers: 192.168.20.1
3) Install DHCP server with one subnet on interface vlan10 and with shared network on interface vlan20
- hosts: all
roles:
- role: dhcp_server
dhcp_common_default_lease_time: 600
dhcp_common_max_lease_time: 7200
dhcp_common_ddns_update_style: none
dhcp_common_authoritative: true
dhcp_common_log_facility: local7
dhcp_subnets:
- base: 192.168.10.0
netmask: 255.255.255.0
interface: vlan10
domain_nameserver: 192.168.10.1
domain_name: example.local
range_start: 192.168.10.150
range_end: 192.168.10.200
routers: 192.168.10.1
dhcp_shared_networks:
- name: sharednet
interface: vlan20
subnets:
- base: 10.7.0.0
netmask: 255.255.255.0
routers: 10.7.0.1
domain_nameserver: 10.7.0.1
domain_name: example.public0
ntp_servers: 10.7.0.1
pools:
- range_start: 10.7.0.2
range_end: 10.7.0.254
- base: 10.8.0.0
netmask: 255.255.255.0
routers: 10.8.0.1
domain_nameserver: 10.8.0.1
domain_name: example.public1
ntp_servers: 10.8.0.1
pools:
- range_start: 10.8.0.2
range_end: 10.8.0.254
Dependencies
------------
None
License
-------
BSD
Author Information
------------------
Philippe Dellaert
---
configure_apparmor: false
dhcp_common_default_lease_time: 600
dhcp_common_max_lease_time: 7200
dhcp_common_authoritative: true
dhcp_common_unknown_clients: true
dhcp_common_ddns_update_style: none
dhcp_ddns_client_updates: true
dhcp_ddns_update_static_leases: true
dhcp_ddns_update_style: interim
dhcp_ddns_updates: true
dhcp_use_ansible_managed: false
dhcp_common_enable_pxe_boot: false
dhcp_interfaces:
dhcp_subnets: []
dhcp_hosts: []
dhcp_classes: []
dhcp_shared_networks: []
backup_dhcpd_conf: no
# Site-specific additions and overrides for usr.sbin.dhcpd.
# For more details, please see /etc/apparmor.d/local/README.
/root/.ansible/tmp/** r,
---
- name: restart dhcpd
service: name={{ dhcp_service }} state=restarted
- name: restart apparmor
service: name={{ apparmor_service }} state=restarted
---
galaxy_info:
author: "Philippe Dellaert"
company: http://dellaert.org
license: BSD
min_ansible_version: 1.4
platforms:
- name: EL
versions:
- 5
- 6
- 7
- name: Fedora
versions:
- 16
- 17
- 18
- name: Ubuntu
versions:
- precise
- quantal
- raring
- saucy
- trusty
- name: Archlinux
versions:
- all
categories:
- system
- networking
dependencies:
- { role: dhcpdstatus }
# Get a list of devices and their properties that have a private subnet range and aren't labeled net or wan
- name: define filtered ethernet facts
set_fact:
my_interfaces: "{{ my_interfaces|default([]) + [hostvars[inventory_hostname]['ansible_' + item]] }}"
when:
- hostvars[inventory_hostname]['ansible_' + item]['device'] is defined
- hostvars[inventory_hostname]['ansible_' + item]['device'].not_icontains('net') and hostvars[inventory_hostname]['ansible_' + item]['device'].not_icontains('wan')
- hostvars[inventory_hostname]['ansible_' + item]['ipv4'] is defined
- hostvars[inventory_hostname]['ansible_' + item]['ipv4']['network'] is defined
- hostvars[inventory_hostname]['ansible_' + item]['ipv4']['network'].startswith('192.168.') or hostvars[inventory_hostname]['ansible_' + item]['ipv4']['network'].startswith('10.')
with_items:
- "{{ hostvars[inventory_hostname]['ansible_interfaces'] }}"
- name: define dhcp specific facts
set_fact:
subnets: "{{ subnets|default([]) | union([{ 'base' : item['ipv4']['network'], 'netmask' : item['ipv4']['netmask'], 'interface' : item['device'], 'range_start' : item['ipv4']['network'] | regex_replace('0$','100'), 'range_end' : item['ipv4']['network'] | regex_replace('0$','250'), 'routers' : item['ipv4']['address'] }]) }}"
with_items:
- "{{ my_interfaces }}"
- name: set dhcp_subnets
set_fact:
dhcp_subnets: "{{ subnets }}"
---
# Loading vars
- name: Add the OS specific varibles
include_vars: "{{ ansible_os_family }}.yml"
- name: debug pre
debug: var=dhcp_subnets
- name: Set host specific varibles if not done manually
include: "dhcp_subnets.yml"
when:
- dhcp_subnets|length == 0
- name: debug post
debug: var=dhcp_subnets
# Install DHCP server
- name: Install the required packages in Redhat derivatives
yum: name={{ dhcp_server_package }} state=present
when: ansible_os_family == 'RedHat'
- name: Install the required packages in Debian derivatives
apt: name={{ dhcp_server_package }} state=present #update_cache=yes
when: ansible_os_family == 'Debian'
- name: Install the required packages in ArchLinux derivatives
pacman: name={{ dhcp_server_package }} state=present update_cache=yes
when: ansible_os_family == 'Archlinux'
# Configuring AppArmor if requested
- name: Check if the /etc/apparmor.d/local folder exists
stat: path=/etc/apparmor.d/local
when: configure_apparmor
register: apparmor_local
- name: Configure AppArmor to allow dhcpd access to temporary ansible files for configuration checking
copy: src=apparmor/usr.sbin.dhcpd dest=/etc/apparmor.d/local/usr.sbin.dhcpd owner=root group=root mode=0644
when: configure_apparmor and apparmor_local.stat.exists
notify:
- restart apparmor
- meta: flush_handlers
# Set desired permissions on /etc/dhcp or use defaults
- name: Set permissions on /etc/dhcp
file: path=/etc/dhcp state=directory mode={{ dhcp_dir_mode | default("0750") }}
when: "ansible_os_family == 'Debian' or ansible_os_family == 'RedHat'"
# Generate reservation
- name: make sure dhcpd-reservations.conf exists
copy: content="" dest={{ dhcp_reservations_config }} force=no owner=root group=root mode=0644
- name: Generate dhcpd-reservations.conf
lineinfile:
path: "{{ dhcp_reservations_config }}"
line: "# {{ item.base }}/{{ item.netmask }} reservations"
with_items: "{{ dhcp_subnets }}"
# Generate configuration
- name: "Is {{ dhcp_server_config }} ansible managed?"
command: grep -Fq "Ansible generated" "{{ dhcp_server_config }}"
register: create_dhcpd
ignore_errors: yes
changed_when: no
check_mode: no
- name: get ddns keys
include: "{{ item }}.yml"
loop: "{{ dhcp_ddns_keys }}"
loop_control:
loop_var: outer_item
when:
- item.source is defined
- name: Generate dhcpd.conf
template: src=dhcpd.conf.j2 dest={{ dhcp_server_config }} owner=root group=root mode=0644 backup={{ backup_dhcpd_conf }}
when: create_dhcpd.rc == 0 or dhcp_use_ansible_managed == true
notify:
- restart dhcpd
# Validate conf (can't do it on generation because dhcpd -t errors on non standard dir locations, maybe apparmor related)
- name: Validate conf
shell: /usr/sbin/dhcpd -t -cf "{{ dhcp_server_config }}"
# Generate service configuration
- name: "Is {{ dhcp_service_config }} ansible managed?"
command: grep -Fq "Ansible generated" "{{ dhcp_service_config }}"
register: create_service
ignore_errors: yes
changed_when: no
check_mode: no
- name: Generate DHCP service conf
template: src=service.conf.{{ ansible_os_family }}.j2 dest={{ dhcp_service_config }} owner=root group=root
when: (ansible_os_family == 'Debian' or (ansible_os_family == 'RedHat' and ansible_distribution_major_version < '7')) and create_service.rc != 0
notify:
- restart dhcpd
# Enable DHCP server
- name: Start the dhcp services DHCP
service: name={{ dhcp_service }} state=started enabled=yes
---
- name: get existing keys
command: pdnsutil list-tsig-keys
register: pdnsutil_list_tsig_keys
- name: get the existing secret
set_fact:
dhcp_ddns_keys: "{{ dhcp_ddns_keys | default({}) | combine( {'value': item.split(' ')[-1]} ) }}"
with_items: "{{ pdnsutil_list_tsig_keys.stdout_lines }}"
when:
- pdnsutil_list_tsig_keys.stdout_lines is defined
- item is search("{{ outer_item.name }}. ")
## dhcpd.conf
{% if dhcp_use_ansible_managed %}# Ansible managed (Do not edit manually, changes will be over written) {% else %}# Ansible generated (No further changes will made while this line exists) {% endif %}
{% if dhcp_omapi_port is defined %}
omapi-port {{ dhcp_omapi_port }};
{% endif %}
# option definitions common to all supported networks...
{% if dhcp_common_domain is defined %}
option domain-name "{{ dhcp_common_domain }}";
{% endif %}
{% if dhcp_common_nameservers is defined %}
option domain-name-servers {{ dhcp_common_nameservers }};
{% endif %}
{% if dhcp_common_default_lease_time is defined %}
default-lease-time {{ dhcp_common_default_lease_time }};
{% endif %}
{% if dhcp_common_max_lease_time is defined %}
max-lease-time {{ dhcp_common_max_lease_time }};
{% endif %}
{{ dhcp_common_unknown_clients | ternary("allow", "ignore") }} unknown-clients;
# Dynamic DNS
ddns-updates {{ dhcp_ddns_updates | ternary("on", "off") }};
ddns-update-style {{ dhcp_ddns_update_style }};
{{ dhcp_ddns_client_updates | ternary("allow", "ignore") }} client-updates;
update-static-leases {{ dhcp_ddns_update_static_leases | ternary("on", "off") }};
{% if dhcp_ddns_keys is defined %}
{% for key in dhcp_ddns_keys %}
key {{ key.name }} {
algorithm hmac-md5;
secret {{ key.value }};
}
{% endfor %}
{% endif %}
{% if dhcp_ddns_zones is defined %}
{% for zone in dhcp_ddns_zones %}
zone {{ zone.name }}. {
primary {{ zone.primary }};
key {{ zone.key }};
}
{% endfor %}
{% endif %}
{% if dhcp_common_authoritative is defined %}
# If this DHCP server is the official DHCP server for the local
# network, the authoritative directive should be uncommented.
authoritative;
{% endif %}
{% if dhcp_common_log_facility is defined %}
# Use this to send dhcp log messages to a different log file (you also
# have to hack syslog.conf to complete the redirection).
log-facility {{ dhcp_common_log_facility }};
{% endif %}
{% if dhcp_common_options is defined %}
{% if dhcp_common_enable_pxe_boot %}
filename "{{ dhcp_common_pxe_boot_file }}";
next-server {{ dhcp_common_pxe_boot_server }};
{% endif %}
#DHCP options
{% for o in dhcp_common_options %}
option {{ o }};
{% endfor %}
{% endif %}
{% if dhcp_common_parameters is defined %}
#DHCP parameters
{% for p in dhcp_common_parameters %}
{{ p }};
{% endfor %}
{% endif %}
{% if dhcp_classes is defined %}
# Classes
{% for c in dhcp_classes %}
class "{{ c.name }}" {
{{ c.rule }};
{% if c.options is defined %}
{% for i in c.options %}
option {{ i.opt }};
{% endfor %}
{% endif %}
}
{% endfor %}
{% endif %}
{% if dhcp_subnets is defined %}
# Subnets
{% for s in dhcp_subnets %}
subnet {{ s.base }} netmask {{ s.netmask }} {
{% if s.interface is defined %}
interface "{{ s.interface }}";
{% endif %}
{% if s.range_start is defined %}
range {{ s.range_start }} {{ s.range_end }};
{% endif %}
{% if s.boot_server is defined %}
option boot-server "{{ s.boot_server }}";
{% endif %}
{% if s.routers is defined %}
option routers {{ s.routers }};
{% endif %}
{% if s.broadcast_address is defined %}
option broadcast-address {{ s.broadcast_address }};
{% endif %}
{% if s.netbios_nameservers is defined %}
option netbios-name-servers {{ s.netbios_nameservers }};
{% endif %}
{% if s.domain_nameservers is defined %}
option domain-name-servers {{ s.domain_nameservers }};
{% endif %}
{% if s.domain_name is defined %}
option domain-name "{{ s.domain_name }}";
{% endif %}
{% if s.ntp_servers is defined %}
option ntp-servers {{ s.ntp_servers }};
{% endif %}
{% if s.default_lease_time is defined %}
default-lease-time {{ s.default_lease_time }};
{% endif %}
{% if s.max_lease_time is defined %}
max-lease-time {{ s.max_lease_time }};
{% endif %}
{% if s.pools is defined %}
{% for p in s.pools %}
pool {
{% if p.rule is defined %}
{{ p.rule }};
{% endif %}
range {{ p.range_start }} {{ p.range_end }};
{% if p.parameters is defined %}
{% for param in p.parameters %}
{{ param }};
{% endfor %}
{% endif %}
}
{% endfor %}
{% endif %}
{% if s.parameters is defined %}
{% for p in s.parameters %}
{{ p }};
{% endfor %}
{% endif %}
}
{% endfor %}
{% endif %}
{% if dhcp_hosts is defined %}
# Hosts
{% for h in dhcp_hosts %}
host {{ h.name }} {
hardware ethernet {{ h.mac_address }};