diff --git a/inventory/hosts.yml b/inventory/hosts.yml index 2a134f7..d4c184a 100644 --- a/inventory/hosts.yml +++ b/inventory/hosts.yml @@ -13,7 +13,9 @@ all: pimp: hosts: pi-mp-1.desu.ltd: + static_ip: 192.168.103.100/16 pi-mp-2.desu.ltd: + static_ip: 192.168.103.101/16 pistorage: hosts: pi-storage-1.desu.ltd: diff --git a/playbooks/pis.yml b/playbooks/pis.yml index a5f4f1f..1b2427d 100755 --- a/playbooks/pis.yml +++ b/playbooks/pis.yml @@ -28,7 +28,7 @@ - name: install openshift pip: name=openshift state=latest tags: [ k8s, packages, pip ] -# Multipurpose Pis doing multipurpose things +# Multipurpose Pis running unbound - hosts: pi-mp-1.desu.ltd,pi-mp-2.desu.ltd vars: container_default_behavior: no_defaults @@ -41,4 +41,13 @@ ports: - "{{ ansible_facts.eth0.ipv4.address }}:53:5053/tcp" - "{{ ansible_facts.eth0.ipv4.address }}:53:5053/udp" - tags: [ pis, mp, docker ] + tags: [ pis, mp, unbound ] +# And also serving DHCP +- hosts: pi-mp-1.desu.ltd + vars_files: + - vars/desulocal-dhcp.yml + roles: + - role: netplan-static-ip + tags: [ pis, mp, dhcp, static-ip ] + - role: dhcp + tags: [ pis, mp, dhcp ] diff --git a/playbooks/vars/desulocal-dhcp.yml b/playbooks/vars/desulocal-dhcp.yml new file mode 100644 index 0000000..62a409e --- /dev/null +++ b/playbooks/vars/desulocal-dhcp.yml @@ -0,0 +1,38 @@ +# vim:ft=ansible: +dhcp_global_default_lease_time: 28800 +dhcp_global_max_lease_time: 43200 +dhcp_global_subnet_mask: 255.255.0.0 +dhcp_global_broadcast_address: 192.168.255.255 +dhcp_global_domain_name: desu.ltd +dhcp_global_domain_name_servers: + - 192.168.103.100 + - 192.168.103.101 +dhcp_subnets: + - ip: 192.168.0.0 + netmask: 255.255.0.0 + pools: + - range_begin: 192.168.1.2 + range_end: 192.168.1.254 + routers: 192.168.1.1 +dhcp_hosts: + # 1 Catch-all guest subnet + - { name: router-nighthawk-1, mac: '10:0C:6B:5A:D9:40', ip: 192.168.1.1 } + # 99 IoT trash + - { name: botnet-roku-1, mac: 'AC:AE:19:58:4B:8F', ip: 192.168.99.1 } + # 100 Others workstations + - { name: sad, mac: '44:A5:6E:42:D2:B9', ip: 192.168.100.1 } + # 101 My workstations + - { name: lap-s76-lemp9-0, mac: '18:56:80:54:77:61', ip: 192.168.101.1 } + - { name: dsk-ryzen-0, mac: '50:76:AF:DF:42:42', ip: 192.168.101.2 } + - { name: ph-flame-0, mac: 'A2:2D:5F:89:E2:AA', ip: 192.168.101.100 } + # 102 k8s cluster + - { name: pi-kub-master-1, mac: 'DC:A6:32:9F:B5:C6', ip: 192.168.102.1 } + - { name: pi-kub-node-2, mac: 'DC:A6:32:A0:14:40', ip: 192.168.102.11 } + - { name: pi-kub-node-3, mac: 'DC:A6:32:A0:13:E6', ip: 192.168.102.12 } + - { name: pi-kub-node-1, mac: 'DC:A6:32:A2:C6:71', ip: 192.168.102.10 } + # 103 Miscellaneous general-purpose + - { name: pi-storage-1, mac: 'DC:A6:32:A0:13:F6', ip: 192.168.103.1 } + - { name: pi-mp-1, mac: 'DC:A6:32:A2:C7:FC', ip: 192.168.103.100 } + - { name: pi-mp-2, mac: 'DC:A6:32:A2:C8:52', ip: 192.168.103.101 } +dhcp_interfaces: + - eth0 diff --git a/roles/dhcp/.gitignore b/roles/dhcp/.gitignore new file mode 100644 index 0000000..0fb91c3 --- /dev/null +++ b/roles/dhcp/.gitignore @@ -0,0 +1,13 @@ +# .gitignore + +# Hidden Vagrant-directory +.vagrant + +# Backup files (e.g. Vim, Gedit, etc.) +*~ + +# Vagrant base boxes (you never know when someone puts one in the repository) +*.box + +# Ignore test code (it's a separate branch worktree) +*tests/ diff --git a/roles/dhcp/.yamllint b/roles/dhcp/.yamllint new file mode 100644 index 0000000..d3f556e --- /dev/null +++ b/roles/dhcp/.yamllint @@ -0,0 +1,21 @@ +--- +# Based on ansible-lint config +extends: default + +rules: + braces: {max-spaces-inside: 1, level: error} + brackets: {max-spaces-inside: 1, level: error} + colons: {max-spaces-after: -1, level: error} + commas: {max-spaces-after: -1, level: error} + comments: disable + comments-indentation: disable + document-start: disable + empty-lines: {max: 3, level: error} + hyphens: {level: error} + indentation: disable + key-duplicates: enable + line-length: disable + new-line-at-end-of-file: disable + new-lines: {type: unix} + trailing-spaces: disable + truthy: disable \ No newline at end of file diff --git a/roles/dhcp/CHANGELOG.md b/roles/dhcp/CHANGELOG.md new file mode 100644 index 0000000..c540ea3 --- /dev/null +++ b/roles/dhcp/CHANGELOG.md @@ -0,0 +1,121 @@ +# Change log + +This file contains all notable changes to the dhcp Ansible role. + +This file adheres to the guidelines of [http://keepachangelog.com/](http://keepachangelog.com/). Versioning follows [Semantic Versioning](http://semver.org/). + +## 3.0.3 - 2020-05-06 + +### Added + +- (GH-40) Added support for RHEL 8, and it's derivatives. (credit: [Stuart Knight](https://github.com/blofeldthefish)) + +## 3.0.2 - 2019-08-29 + +### Added + +- (GH-29) The ability to add customised config snippets, whilst using a locally defined (outside of this role) Jinja Template. (credit: [Alex Gittings](https://github.com/minitriga)) + +## 3.0.1 - 2019-08-14 + +### Changed + +- Fix ansible-lint warnings +- Update documentation for failover peer documentation + +## 3.0.0 - 2019-08-14 + +### Added + +- (GH-18) The ability to add multiple subnet ranges to a scope. (credit: [Stuart Knight](https://github.com/blofeldthefish)) +- (GH-24) Add parameter `dhcp_apparmor_fix` to enable/disable the AppArmor fix (credit: [Maxim Baranov](https://github.com/mbaran0v)) +- Variable `dhcp_pxeboot_server` in order to allow this role to refer PXEBoot clients to the correct PXEBoot server. + +### Changed + +- (GH-19) **Breaking change** Fix inconsistency with readme for omapi secret. In the README the `dhcp_global_omapi_secret` is defined as such, whereas in the template it is `dhcp_omapi_secret`. It *should* be `dhcp_global_omapi_secret`. This will break playbooks that use the `dhcp_omapi_secret` variable. +- (GH-21, GH-25) Define network device in /etc/defaults. This is needed on Debian based distros. +- (GH-22) Support `include` lines for non-existent files in role's `files/` directory. This allows the user to add `include` lines in dhcpd.conf for non-existent files; files not found in role's `files/` directory. It should permit successful configuration of `dhcpd.conf` with the expectation of another process (role, task, legacy method, etc.) to provide the include file. (credit: [RayfordJ](https://github.com/rayfordj)) +- (GH-23) Removed default value for `dhcp_global_other_options` and test for its definition in the config file template. This is more consistent with how the other role variables are handled in the config file template. (credit: [lijok](https://github.com/lijok)) +- (GH-26) Fixed typo in README (credit [Guillaume Parent](https://github.com/gparent)) +- (GH-27) Use list of packages directly instead of in a `with_items` loop. (credit [Guillaume Parent](https://github.com/gparent)) +- Increased minimum Ansible version to 2.8 due to usage of more recent Ansible syntax (e.g. package installation directly with variable containing list of packages instead of `with_items` loop). +- Updated list of supported versions to latest stable releases of tested distros (EL 7.6, Fedora 30, Ubuntu 18.04) +- Use Yamllint configuration from Ansible Galaxy and fix Yamllint warnings +- Updated Vagrant test environment, in new orphan branch `vagrant-tests`. + +## 2.2.0 - 2018-10-13 + +### Added + +- (GH-13,14) support fixed address hosts in subnets (credit: [Ahmed Sghaier](https://github.com/asghaier)) +- (GH-15) Add variable `dhcp_service_state`, to define the desired state of the service (default: started). (credit: [Alessandro Ogier](https://github.com/aogier)) +- (GH-17) New configuration items for failover peer: `address`, `failover_peer`, `hba`, `load_balance_max_seconds`, `max-balance`, `max-lease-misbalance`, `max-lease-ownership`, `max_response_delay`, `max_unacked_updates`, `mclt`, `min-balance`, `peer_address`, `peer_port`, `port`, `role`, `split` (credit: [cacheira](https://github.com/cacheira)) + +### Changed + +- (GH-11,12) The `domain_search` key of `dhcp_subnets` can now also be a list (credit: [Ahmed Sghaier](https://github.com/asghaier)) +- (GH-16) Allow host declaration without specifying `fixed-address`. (credit: [Alessandro Ogier](https://github.com/aogier)) + +## 2.1.2 - 2017-11-21 + +### Changed + +- Fixed Ansible 2.4 deprecation warnings (include: -> include_tasks) + +## 2.1.1 - 2017-07-03 + +### Changed + +- (GH-10) Fixed bug where playbook run fails because `dhcp_global_includes` is undefined + +## 2.1.0 - 2017-06-26 + +### Added + +- New configuration items: + - (GH-7) `dhcp_global_log_facility`, `dhcp_global_server_name`, `dhcp_global_authoritative` (credit: [@jpiron](https://github.com/jpiron)) + - `dhcp_global_ntp_servers`, `dhcp_global_includes` (credit: Felix Egli) + +- (GH-9) Support OMAPI keys and catch-all options (credit: [@joshbenner](https://github.com/joshbenner)) + +### Changed + +- (GH-7) Several improvements: package state as variable instead of hard-coded, made host declarations global (credit: [@jpiron](https://github.com/jpiron) +- (GH-8) Fixed typo in README (credit: [@donvipre](https://github.com/donvipre) +- Quoted values in `dhcp_global_domain_search` (credit: Felix Egli) + +## 2.0.0 - 2016-04-29 + +### Added + +- Support for Ubuntu LTS 14.04 (Trusty Tahr) and 16.04 (Xenial Xerus) +- Tested on Fedora 23 and CentOS 6, and added to supported platforms + +### Changed + +- This version now uses the general package management module introduced in Ansible 2.0. This is considered a breaking change, since it wil no longer work with Ansible 1.6-1.7. + +## 1.1.0 - 2016-04-28 + +### Added + +- Support for PXE boot parameters bootp, booting, next-server, filename. Credits to [Rian Bogle](https://github.com/rbogle) +- Address pools within subnet declaration. Credits to [Birgit Croux](https://github.com/birgitcroux) +- Definition of classes with match statements + +## 1.0.1 - 2015-08-28 + +### Changed + +- Fixed a tag name +- Fixed GH-1: domain name no longer needs to be "double quoted" + +## 1.0.0 - 2015-08-24 + +First release! + +### Added + +- Allow setting some global variables +- Subnet declarations in YAML diff --git a/roles/dhcp/LICENSE.md b/roles/dhcp/LICENSE.md new file mode 100644 index 0000000..8411892 --- /dev/null +++ b/roles/dhcp/LICENSE.md @@ -0,0 +1,13 @@ +# BSD License + +Copyright (c) 2014, Bert Van Vreckem, (bert.vanvreckem@gmail.com) + +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. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT HOLDER OR CONTRIBUTORS 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. diff --git a/roles/dhcp/README.md b/roles/dhcp/README.md new file mode 100644 index 0000000..e28a903 --- /dev/null +++ b/roles/dhcp/README.md @@ -0,0 +1,316 @@ +# Ansible role `dhcp` + +Ansible role for setting up ISC DHCPD. The responsibilities of this role are to install packages and manage the configuration ([dhcpd.conf(5)](http://linux.die.net/man/5/dhcpd.conf)). Managing the firewall configuration is NOT a concern of this role. You can do this in your local playbook, or use another role (e.g. [bertvv.rh-base](https://galaxy.ansible.com/bertvv/rh-base). + +Refer to the [change log](CHANGELOG.md) for notable changes in each release. + +Do you use/like this role? Please consider giving it a star. If you [rate this role](https://galaxy.ansible.com/bertvv/dhcp) on Ansible Galaxy and find it lacking in some respect, please consider opening an Issue with actionable feedback or a PR so we can improve it. Thank you! + +## Requirements + +No specific requirements + +## Role Variables + +This role is able to set global options, and to specify subnet declarations. + +See the [test playbook](https://github.com/bertvv/ansible-role-dhcp/blob/vagrant-tests/test.yml) for a working example of a DHCP server in a test environment based on Vagrant and VirtualBox. This section is a reference of all supported options. + +### Global options + +The following variables, when set, will be added to the global section of the DHCP configuration file. If there is no default value specified, the corresponding setting will be left out of `dhcpd.conf(5)`. + +See the [dhcp-options(5)](http://linux.die.net/man/5/dhcp-options) man page for more information about these options. + +| Variable | Comments | +| :--- | :--- | +| `dhcp_global_authoritative` | Global authoritative statement (`authoritative`, `not authoritative`) | +| `dhcp_global_booting` | Global booting (`allow`, `deny`, `ignore`) | +| `dhcp_global_bootp` | Global bootp (`allow`, `deny`, `ignore`) | +| `dhcp_global_broadcast_address` | Global broadcast address | +| `dhcp_global_classes` | Class definitions with a match statement(1) | +| `dhcp_global_default_lease_time` | Default lease time in seconds | +| `dhcp_global_domain_name_servers` | A list of IP addresses of DNS servers(2) | +| `dhcp_global_domain_name` | The domain name the client should use when resolving host names | +| `dhcp_global_domain_search` | A list of domain names to be used by the client to locate non-FQDNs(1) | +| `dhcp_global_failover` | Failover peer settings (3) | +| `dhcp_global_failover_peer` | Name for the failover peer (e.g. `foo`) | +| `dhcp_global_filename` | Filename to request for boot | +| `dhcp_global_includes_missing` | Boolean. Continue if `includes` file(s) missing from role's files/ | +| `dhcp_global_includes` | List of config files to be included (from `dhcp_config_dir`) | +| `dhcp_global_log_facility` | Global log facility (e.g. `daemon`, `syslog`, `user`, ...) | +| `dhcp_global_max_lease_time` | Maximum lease time in seconds | +| `dhcp_global_next_server` | IP for PXEboot server | +| `dhcp_global_ntp_servers` | List of IP addresses of NTP servers | +| `dhcp_global_omapi_port` | OMAPI port | +| `dhcp_global_omapi_secret` | OMAPI secret | +| `dhcp_global_other_options` | Array of arbitrary additional global options | +| `dhcp_global_routers` | IP address of the router | +| `dhcp_global_server_name` | Server name sent to the client | +| `dhcp_global_server_state` | Service state (started, stopped) | +| `dhcp_global_subnet_mask` | Global subnet mask | +| `dhcp_custom_includes` | List of jinja config files to be included (from `dhcp_config_dir`) | + +**Remarks** + +(1) This role supports the definition of classes with a match statement, e.g.: + +```Yaml +# Class for VirtualBox VMs +dhcp_global_classes: + - name: vbox + match: 'match if binary-to-ascii(16,8,":",substring(hardware, 1, 3)) = "8:0:27"' +``` + +Class names can be used in the definition of address pools (see below). + +(2) The role variable `dhcp_global_domain_name_servers` may be written either as a list (when you have more than one item), or as a string (when you have only one). The following snippet shows an example of both: + +```Yaml +# A single DNS server +dhcp_global_domain_name_servers: 8.8.8.8 + +# A list of DNS servers +dhcp_global_domain_name_servers: + - 8.8.8.8 + - 8.8.4.4 +``` + +(3) This role also supports the definition of a failover peer, e.g.: + +```Yaml +# Failover peer definition +dhcp_global_failover_peer: failover-group +dhcp_global_failover: + role: primary # | secondary + address: 192.168.222.2 + port: 647 + peer_address: 192.168.222.3 + peer_port: 647 + max_response_delay: 15 + max_unacked_updates: 10 + load_balance_max_seconds: 5 + split: 255 + mclt: 3600 +``` + +The variable `dhcp_global_failover_peer` contains a name for the configured peer, to be used on a per pool basis. The failover declaration options are specified with the variable `dhcp_global_failover`, a dictionary that may contain the following options: + +| Option | Required | Comment | +| :--- | :---: | :-- | +| `address` | no | This server's IP address | +| `hba` | no | colon-separated-hex-list | +| `load_balance_max_seconds` | no | Cutoff after which load balance is disabled (3 to 5 recommended) | +| `max-balance` | no | Failover pool balance statement | +| `max-lease-misbalance` | no | Failover pool balance statement | +| `max-lease-ownership` | no | Failover pool balance statement | +| `max_response_delay` | no | Maximum seconds without contact before engaging failover | +| `max_unacked_updates` | no | Maximum BNDUPD it can send before receiving a BNDACK (10 recommended) | +| `mclt` | no | Maximum Client Lead Time | +| `min-balance` | no | Failover pool balance statement | +| `peer_address` | no | Failover peer's IP addres | +| `peer_port` | no | This server's port (generally 519/520 or 647/847) | +| `port` | no | This server's port (generally 519/520 or 647/847) | +| `role` | no | primary, secondary | +| `split` | no | Load balance split (0-255) | + +The failover peer directive has to be in the definition of address pools (see below). + +### Subnet declarations + +The role variable `dhcp_subnets` contains a list of dicts, specifying the subnet declarations to be added to the DHCP configuration file. Every subnet declaration should have an `ip` and `netmask`, other options are not mandatory. We start this section with an example, a complete overview of supported options follows. + +```Yaml +dhcp_subnets: + - ip: 192.168.222.0 + netmask: 255.255.255.128 + domain_name_servers: + - 10.0.2.3 + - 10.0.2.4 + range_begin: 192.168.222.50 + range_end: 192.168.222.127 + - ip: 192.168.222.128 + default_lease_time: 3600 + max_lease_time: 7200 + netmask: 255.255.255.128 + domain_name_servers: 10.0.2.3 + routers: 192.168.222.129 +``` + +An alphabetical list of supported options in a subnet declaration: + +| Option | Required | Comment | +| :--- | :---: | :-- | +| `booting` | no | allow,deny,ignore | +| `bootp` | no | allow,deny,ignore | +| `default_lease_time` | no | Default lease time for this subnet (in seconds) | +| `domain_name_servers` | no | List of domain name servers for this subnet(1) | +| `domain_search` | no | List of domain names for resolution of non-FQDNs(1) | +| `filename` | no | filename to retrieve from boot server | +| `hosts` | no | List of fixed IP address hosts for each subnet, similar to dhcp_hosts | +| `ip` | yes | **Required.** IP address of the subnet | +| `max_lease_time` | no | Maximum lease time for this subnet (in seconds) | +| `netmask` | yes | **Required.** Network mask of the subnet (in dotted decimal notation) | +| `next_server` | no | IP address of the boot server | +| `ntp_servers` | no | List of NTP servers for this subnet | +| `range_begin` | no | Lowest address in the range of dynamic IP addresses to be assigned | +| `range_end` | no | Highest address in the range of dynamic IP addresses to be assigned | +| `ranges` | no | If multiple ranges are needed, they can be specified as a list (2) | +| `routers` | no | IP address of the gateway for this subnet | +| `server_name` | no | Server name sent to the client | +| `subnet_mask` | no | Overrides the `netmask` of the subnet declaration | + +You can specify address pools within a subnet by setting the `pools` options. This allows you to specify a pool of addresses that will be treated differently than another pool of addresses, even on the same network segment or subnet. It is a list of dicts with the following keys, all of which are optional: + +| Option | Comment | +| :--- | :--- | +| `allow` | Specifies which hosts are allowed in this pool(1) | +| `default_lease_time` | The default lease time for this pool | +| `deny` | Specifies which hosts are not allowed in this pool | +| `domain_name_servers` | The domain name servers to be used for this pool(1) | +| `max_lease_time` | The maximum lease time for this pool | +| `min_lease_time` | The minimum lease time for this pool | +| `range_begin` | The lowest address in this pool | +| `range_end` | The highest address in this pool | +| `ranges` | If multiple ranges are needed, they can be specified as a list (2) | + +(1) For the `allow` and `deny` fields, the options are enumerated in [dhcpd.conf(5)](http://linux.die.net/man/5/dhcpd.conf), but include: + +- `booting` +- `bootp` +- `client-updates` +- `known-clients` +- `members of "CLASS"` +- `unknown-clients` + +(2) For multiple subnet ranges, they can be specified, thus: + +```Yaml +ranges: + - { begin: 192.168.222.50, end: 192.168.222.99 } + - { begin: 192.168.222.110, end: 192.168.222.127 } +``` + +### Host declarations + +You can specify hosts that should get a fixed IP address based on their MAC by setting the `dhcp_hosts` option. This is a list of dicts with the following three keys, of which `name` and `mac` are mandatory: + +| Option | Comment | +| :--- | :--- | +| `name` | The name of the host | +| `mac` | The MAC address of the host | +| `ip` | The IP address to be assigned to the host | + +```Yaml +dhcp_hosts: + - name: cl1 + mac: '00:11:22:33:44:55' + ip: 192.168.222.150 + - name: cl2 + mac: '00:de:ad:be:ef:00' + ip: 192.168.222.151 +``` + +### Specify PXEBoot server + +Setting the variable `dhcp_pxeboot_server`, will redirect PXE clients to the specified PXEBoot server in order to boot over the network. The specified server should have boot images on the expected locations. Use e.g. [bertvv.pxeserver](https://galaxy.ansible.com/bertvv/pxeserver) to configure it. + +### Custom Includes + +Setting the variable `dhcp_custom_inludes` to a jinja template will allow custom configurations to be used which will subsequently be included into the `dhcpd.conf` file. If the template file name has the `.j2` extension it will be removed from the destination file name, else it will preserve the template file name in the destination. + +```Yaml +dhcp_custom_includes: + - custom-dhcp-config.conf[.j2] +``` + +You can create your own variables to use within the template allowing for total flexibility. To avoid variable conflicts make sure that you use variables that are not referenced within this role as this will duplicate configuration in multiple `.conf` files. + +```Yaml + dhcp_custom_hosts: + - name: Juniper1 + mac: 'de:ad:c0:de:ca:fe' + ip: 192.168.35.160 + options: + - name: tftp-server-name + value: 192.168.35.152 + - name: host-name + value: Juniper1 + - name: NEW_OP.transfer-mode + value: "http" + - name: NEW_OP.config-file-name + value: "/configurations/j1-switch.config" +``` + +Finally the jinja template must contain valid ISC DHCPD configuration ([dhcpd.conf(5)](http://linux.die.net/man/5/dhcpd.conf)). This is an example of using [bertvv.dhcp](https://galaxy.ansible.com/bertvv/dhcp) for juniper Zero-Touch-Provisioning. + +```Jinja +option space NEW_OP; +option NEW_OP.image-file-name code 0 = text; +option NEW_OP.config-file-name code 1 = text; +option NEW_OP.image-file-type code 2 = text; +option NEW_OP.transfer-mode code 3 = text; +option NEW_OP.alt-image-file-name code 4= text; +option NEW_OP.http-port code 5= text; +option NEW_OP-encapsulation code 43 = encapsulate NEW_OP; + +{% if dhcp_custom_hosts is defined %} + +# +# Host declarations +# +{% for host in dhcp_custom_hosts %} +host {{ host.name | replace (" ","_") | replace ("'","_") | replace (":","_") }} { + hardware ethernet {{ host.mac }}; +{% if host.ip is defined %} + fixed-address {{ host.ip }}; +{% endif %} +{% if host.options is defined %} +{% for option in host.options %} + {{ option.name }} "{{ option.value }}" +{% endfor %} +{% endif %} +} +{% endfor %} +{% endif %} +``` + +## Dependencies + +No dependencies. + +## Example Playbook + +See the [test playbook](https://github.com/bertvv/ansible-role-dhcp/blob/vagrant-tests/test.yml) + +## Testing + +Tests for this role are provided in the form of a Vagrant environment that is kept in a separate branch, `vagrant-tests`. For more information about setting up the test environment and running the tests, refer to the [README](https://github.com/bertvv/ansible-role-dhcp/blob/vagrant-tests/README.md) of the test branch. + +## License + +BSD + +## Contributing + +Issues, feature requests, ideas are appreciated and can be posted in the Issues section. Pull requests are also very welcome. Preferably, create a topic branch and when submitting, squash your commits into one (with a descriptive message). + +### Contributors + +- [Ahmed Sghaier](https://github.com/asghaier) +- [Alessandro Ogier](https://github.com/aogier) +- [Alex Gittings](https://github.com/minitriga) +- [Bert Van Vreckem](https://github.com/bertvv) (maintainer) +- [Birgit Croux](https://github.com/birgitcroux/) +- [@cacheira](https://github.com/cacheira) +- [@donvipre](https://github.com/donvipre) +- Felix Egli +- [Guillaume Parent](https://github.com/gparent) +- [Jonathan Piron](https://github.com/jpiron) +- [Josh Benner](https://github.com/joshbenner) +- [@jpiron](https://github.com/jpiron) +- [@lijok](https://github.com/lijok) +- [Maxim Baranov](https://github.com/mbaran0v) +- [@RayfordJ](https://github.com/rayfordj) +- [Rian Bogle](https://github.com/rbogle/) +- [Stuart Knight](https://github.com/blofeldthefish) (maintainer) diff --git a/roles/dhcp/defaults/main.yml b/roles/dhcp/defaults/main.yml new file mode 100644 index 0000000..c52acbb --- /dev/null +++ b/roles/dhcp/defaults/main.yml @@ -0,0 +1,7 @@ +# roles/dhcp/defaults/main.yml +--- + +dhcp_apparmor_fix: true +dhcp_global_includes_missing: false +dhcp_packages_state: "present" +dhcp_subnets: [] diff --git a/roles/dhcp/handlers/main.yml b/roles/dhcp/handlers/main.yml new file mode 100644 index 0000000..bd8038e --- /dev/null +++ b/roles/dhcp/handlers/main.yml @@ -0,0 +1,12 @@ +# roles/dhcp/handlers/main.yml +--- + +- name: restart dhcp + service: + name: "{{ dhcp_service }}" + state: "{{ (dhcp_global_server_state | default('started') == 'started') | ternary('restarted', 'stopped') }}" + +- name: restart apparmor + service: + name: apparmor + state: restarted diff --git a/roles/dhcp/meta/.galaxy_install_info b/roles/dhcp/meta/.galaxy_install_info new file mode 100644 index 0000000..afc4bbe --- /dev/null +++ b/roles/dhcp/meta/.galaxy_install_info @@ -0,0 +1,2 @@ +install_date: Sat Feb 27 13:38:57 2021 +version: master diff --git a/roles/dhcp/meta/main.yml b/roles/dhcp/meta/main.yml new file mode 100644 index 0000000..c058b6e --- /dev/null +++ b/roles/dhcp/meta/main.yml @@ -0,0 +1,21 @@ +--- +galaxy_info: + author: Bert Van Vreckem + description: Ansible role for setting up ISC DHCPD. + license: BSD + min_ansible_version: 2.8 + platforms: + - name: EL + versions: + - 7 + - 8 + - name: Fedora + versions: + - 29 + - name: Ubuntu + versions: + - bionic + galaxy_tags: + - system + - networking +dependencies: [] diff --git a/roles/dhcp/tasks/apparmor-fix.yml b/roles/dhcp/tasks/apparmor-fix.yml new file mode 100644 index 0000000..1723123 --- /dev/null +++ b/roles/dhcp/tasks/apparmor-fix.yml @@ -0,0 +1,38 @@ +# roles/dhcp/tasks/apparmor-fix.yml +# This playbook adds an AppArmor policy rule that allows the dhcpd process to +# acces temporary config files copied to the server by Ansible. +--- + +- name: AppArmor fix | Check if policy file exists + stat: + path: "{{ dhcp_apparmor_policy }}" + register: apparmor_policyfile + tags: dhcp + +- name: AppArmor fix | Ensure dhcpd can acces temp config file for validation (1/2) + lineinfile: + dest: "{{ dhcp_apparmor_policy }}" + line: ' capability dac_override,' + insertafter: ' capability setuid,' + state: present + create: false + when: apparmor_policyfile.stat.exists + failed_when: false + notify: restart apparmor + tags: dhcp + +- name: AppArmor fix | Ensure dhcpd can acces temp config file for validation (2/2) + lineinfile: + dest: "{{ dhcp_apparmor_policy }}" + line: ' /home/*/.ansible/** r,' + insertbefore: '.*/etc/dhcp/ r,' + state: present + create: false + when: apparmor_policyfile.stat.exists + failed_when: false + #register: apparmor_fix_2 + notify: restart apparmor + tags: dhcp + +- name: AppArmor fix | Force running handlers now + meta: flush_handlers diff --git a/roles/dhcp/tasks/default-fix.yml b/roles/dhcp/tasks/default-fix.yml new file mode 100644 index 0000000..11deb7e --- /dev/null +++ b/roles/dhcp/tasks/default-fix.yml @@ -0,0 +1,11 @@ +# roles/dhcp/tasks/default-fix.yml +# This playbook adjusts a required dhcp package "default" file, +# specific to debian-like installs +--- + +- name: Defaults fix | Set a default listening interface + lineinfile: + dest: /etc/default/isc-dhcp-server + line: 'INTERFACESv4="{{ dhcp_interfaces | default(ansible_default_ipv4.interface) }}"' + regexp: '^INTERFACESv4=' + tags: dhcp diff --git a/roles/dhcp/tasks/main.yml b/roles/dhcp/tasks/main.yml new file mode 100644 index 0000000..3784ea7 --- /dev/null +++ b/roles/dhcp/tasks/main.yml @@ -0,0 +1,70 @@ +# roles/dhcp/tasks/main.yml +--- + +- name: Load distro-specific variables + include_vars: "{{ item }}" + with_first_found: + - "{{ ansible_distribution }}.yml" + - "{{ ansible_os_family }}.yml" + - "{{ default }}.yml" + tags: dhcp + +- name: Install packages + package: + name: "{{ dhcp_packages }}" + state: "{{ dhcp_packages_state }}" + tags: dhcp + +- include_tasks: apparmor-fix.yml + when: ansible_os_family == 'Debian' and dhcp_apparmor_fix|bool + tags: dhcp + +- include_tasks: default-fix.yml + when: ansible_os_family == 'Debian' + tags: dhcp + +- name: Install custom includes + template: + src: "{{ item }}" + dest: "{{ dhcp_config_dir }}/{{ ( item | basename ).split('.j2')[0] }}" + owner: root + group: root + mode: 0644 + with_items: "{{ dhcp_custom_includes }}" + when: dhcp_custom_includes is defined + notify: restart dhcp + tags: dhcp + +- name: Install includes + copy: + src: "{{ item }}" + dest: "{{ dhcp_config_dir }}/{{ item | basename }}" + with_items: "{{ dhcp_global_includes }}" + when: dhcp_global_includes is defined + ignore_errors: "{{ dhcp_global_includes_missing }}" + tags: dhcp + +- name: Set config directory perms + file: + path: "{{ dhcp_config | dirname }}" + state: directory + mode: 0755 + tags: dhcp + +- name: Install config file + template: + src: etc_dhcp_dhcpd.conf.j2 + dest: "{{ dhcp_config }}" + owner: root + group: root + mode: 0644 + validate: 'dhcpd -t -cf %s' + notify: restart dhcp + tags: dhcp + +- name: "Ensure service is {{ dhcp_global_server_state | default('started') }}" + service: + name: "{{ dhcp_service }}" + state: "{{ dhcp_global_server_state | default('started') }}" + enabled: true + tags: dhcp diff --git a/roles/dhcp/templates/etc_dhcp_dhcpd.conf.j2 b/roles/dhcp/templates/etc_dhcp_dhcpd.conf.j2 new file mode 100644 index 0000000..3a21dbe --- /dev/null +++ b/roles/dhcp/templates/etc_dhcp_dhcpd.conf.j2 @@ -0,0 +1,317 @@ +# ISC DHCPD configuration -- don't edit manually! +# +# {{ ansible_managed }} + +# +# Global options +# +{% if dhcp_global_omapi_port is defined %} +omapi-port {{ dhcp_global_omapi_port }}; +{% endif %} +{% if dhcp_global_omapi_secret is defined %} +key omapi_key { + algorithm HMAC-MD5; + secret "{{ dhcp_global_omapi_secret }}"; +}; +{% endif %} +{% if dhcp_global_authoritative is defined %} +{{ dhcp_global_authoritative }}; +{% endif %} +{% if dhcp_global_log_facility is defined %} +log-facility {{ dhcp_global_log_facility }}; +{% endif %} +{% if dhcp_global_bootp is defined %} +{{ dhcp_global_bootp }} bootp; +{% endif %} +{% if dhcp_global_booting is defined %} +{{ dhcp_global_booting }} booting; +{% endif %} +{% if dhcp_global_next_server is defined %} +next-server {{ dhcp_global_next_server}}; +{% endif %} +{% if dhcp_global_filename is defined %} +filename "{{ dhcp_global_filename }}"; +{% endif %} +{% if dhcp_global_default_lease_time is defined %} +default-lease-time {{ dhcp_global_default_lease_time }}; +{% endif %} +{% if dhcp_global_max_lease_time is defined %} +max-lease-time {{ dhcp_global_max_lease_time }}; +{% endif %} +{% if dhcp_global_subnet_mask is defined %} +option subnet-mask {{ dhcp_global_subnet_mask }}; +{% endif %} +{% if dhcp_global_broadcast_address is defined %} +option broadcast-address {{ dhcp_global_broadcast_address }}; +{% endif %} +{% if dhcp_global_routers is defined %} +option routers {{ dhcp_global_routers }}; +{% endif %} +{% if dhcp_global_domain_name is defined %} +option domain-name "{{ dhcp_global_domain_name }}"; +{% endif %} +{% if dhcp_global_ntp_servers is defined %} +{% if dhcp_global_ntp_servers is string %} +option ntp-servers {{ dhcp_global_ntp_servers }}; +{% else %} +option ntp-servers {{ dhcp_global_ntp_servers|join(', ') }}; +{% endif %} +{% endif %} +{% if dhcp_global_domain_name_servers is defined %} +{% if dhcp_global_domain_name_servers is string %} +option domain-name-servers {{ dhcp_global_domain_name_servers }}; +{% else %} +option domain-name-servers {{ dhcp_global_domain_name_servers|join(', ') }}; +{% endif %} +{% endif %} +{% if dhcp_global_domain_search is defined %} +{% if dhcp_global_domain_search is string %} +option domain-search "{{ dhcp_global_domain_search }}"; +{% else %} +option domain-search "{{ dhcp_global_domain_search|join('", "') }}"; +{% endif %} +{% endif %} +{% if dhcp_global_server_name is defined %} +option server-name "{{ dhcp_global_server_name }}"; +{% endif %} +{% if dhcp_global_other_options is defined %} +{% for option in dhcp_global_other_options %} +option {{ option }}; +{% endfor %} +{% endif %} +{% if dhcp_global_failover_peer is defined %} + +# +# DHCP Failover config +# +# Notes: In the past couple years, TCP ports 647 (primary) and 847 (peer) have +# emerged as the standard bindings for DHCP dhcp_global_failover It is worth noting that as +# recently as 2005, the dhcpd.conf(5) man page used ports 519 and 520 in its +# failover example, but 647 and 847 look like good choices as of 2008. However, +# the dhcpd.conf(5) man page says that the primary port and the peer port may be +# the same number. + +failover peer "{{ dhcp_global_failover_peer }}" { +{% if dhcp_global_failover.role is defined %} + # [ primary | secondary ]; + {{ dhcp_global_failover.role }}; +{% endif %} +{% if dhcp_global_failover.address is defined %} + address {{ dhcp_global_failover.address }}; +{% endif %} +{% if dhcp_global_failover.port is defined %} + port {{ dhcp_global_failover.port }}; +{% endif %} +{% if dhcp_global_failover.peer_address is defined %} + peer address {{ dhcp_global_failover.peer_address }}; +{% endif %} +{% if dhcp_global_failover.peer_port is defined %} + peer port {{ dhcp_global_failover.peer_port }}; +{% endif %} +{% if dhcp_global_failover.max_response_delay is defined %} + max-response-delay {{ dhcp_global_failover.max_response_delay }}; +{% endif %} +{% if dhcp_global_failover.max_unacked_updates is defined %} + max-unacked-updates {{ dhcp_global_failover.max_unacked_updates }}; +{% endif %} +{% if dhcp_global_failover.split is defined %} + split {{ dhcp_global_failover.split }}; +{% endif %} +{% if dhcp_global_failover.hba is defined %} + hba {{ dhcp_global_failover.hba }}; +{% endif %} +{% if dhcp_global_failover.mclt is defined %} + mclt {{ dhcp_global_failover.mclt }}; +{% endif %} +{% if dhcp_global_failover.load_balance_max_seconds is defined %} + load balance max seconds {{ dhcp_global_failover.load_balance_max_seconds }}; +{% endif %} +{% if dhcp_global_failover.max_lease_misbalance is defined %} + max-lease-misbalance {{ dhcp_global_failover.max_lease_misbalance }}; +{% endif %} +{% if dhcp_global_failover.max_lease_ownership is defined %} + max-lease-ownership {{ dhcp_global_failover.max_lease_ownership }}; +{% endif %} +{% if dhcp_global_failover.min_balance is defined %} + min-balance {{ dhcp_global_failover.min_balance }}; +{% endif %} +{% if dhcp_global_failover.max_balance is defined %} + max-balance {{ dhcp_global_failover.max_balance }}; +{% endif %} +} +{% endif %} +{% if dhcp_global_includes is defined %} +# +# Includes +# +{% for include in dhcp_global_includes %} +include "{{ dhcp_config_dir }}/{{ include | basename }}"; +{% endfor %} +{% endif %} + +{% if dhcp_custom_includes is defined%} +# +# Custom Includes +# +{% for include in dhcp_custom_includes %} +include "{{ dhcp_config_dir }}/{{ ( include | basename ).split('.j2')[0] }}"; +{% endfor %} +{% endif %} + +{% if dhcp_global_classes is defined %} +# +# Classes +# +{% for class in dhcp_global_classes %} +class "{{ class.name }}" { +{% if class.match is defined %} + {{ class.match }}; +{% endif %} +} +{% endfor %} +{% endif %} +# +# Subnet declarations +# +{% for subnet in dhcp_subnets %} +subnet {{ subnet.ip }} netmask {{ subnet.netmask }} { +{% if subnet.default_lease_time is defined %} + default-lease-time {{ subnet.default_lease_time }}; +{% endif %} +{% if subnet.max_lease_time is defined %} + max-lease-time {{ subnet.max_lease_time }}; +{% endif %} +{% if subnet.routers is defined %} + option routers {{ subnet.routers }}; +{% endif %} +{% if subnet.subnet_mask is defined %} + option subnet-mask {{ subnet.subnet_mask }}; +{% endif %} +{% if subnet.domain_search is defined %} +{% if subnet.domain_search is string %} + option domain-search "{{ subnet.domain_search }}"; +{% else %} + option domain-search "{{ subnet.domain_search|join('", "') }}"; +{% endif %} +{% endif %} +{% if subnet.domain_name_servers is defined %} +{% if subnet.domain_name_servers is string %} + option domain-name-servers {{ subnet.domain_name_servers }}; +{% else %} + option domain-name-servers {{ subnet.domain_name_servers|join(', ') }}; +{% endif %} +{% endif %} +{% if subnet.ntp_servers is defined %} +{% if subnet.ntp_servers is string %} +option ntp-servers {{ subnet.ntp_servers }}; +{% else %} +option ntp-servers {{ subnet.ntp_servers|join(', ') }}; +{% endif %} +{% endif %} +{% if subnet.range_begin is defined and subnet.range_end is defined %} + range {{ subnet.range_begin }} {{ subnet.range_end }}; +{% endif %} +{% if subnet.ranges is defined %} +{% for range in subnet.ranges %} + range {{ range.begin }} {{ range.end }}; +{% endfor %} +{% endif %} +{% if subnet.server_name is defined %} + server-name {{ subnet.server_name }}; +{% endif %} +{% if subnet.next_server is defined %} + next-server {{ subnet.next_server }}; +{% endif %} +{% if subnet.filename is defined %} + filename "{{ subnet.filename }}"; +{% endif %} +{% if subnet.bootp is defined %} +{{ subnet.bootp }} bootp; +{% endif %} +{% if subnet.booting is defined %} +{{ subnet.booting }} booting; +{% endif %} +{% if subnet.hosts is defined %} +{% for host in subnet.hosts %} + host {{ host.name }} { + hardware ethernet {{ host.mac }}; + fixed-address {{ host.ip }}; + } +{% endfor %} +{% endif %} +{% if subnet.pools is defined %} + # Address pool(s) +{% for pool in subnet.pools %} + pool { +{% if pool.failover_peer is defined %} +# This pool has failover, see above for server details + failover peer "{{ pool.failover_peer }}"; +{% endif %} +{% if pool.domain_name_servers is defined %} +{% if pool.domain_name_servers is string %} + option domain-name-servers {{ pool.domain_name_servers }}; +{% else %} + option domain-name-servers {{ pool.domain_name_servers|join(', ') }}; +{% endif %} +{% endif %} +{% if pool.default_lease_time is defined %} + default-lease-time {{ pool.default_lease_time }}; +{% endif %} +{% if pool.min_lease_time is defined %} + min-lease-time {{ pool.min_lease_time }}; +{% endif %} +{% if pool.max_lease_time is defined %} + max-lease-time {{ pool.max_lease_time }}; +{% endif %} +{% if pool.range_begin is defined and pool.range_end is defined %} + range {{ pool.range_begin }} {{ pool.range_end }}; +{% endif %} +{% if pool.ranges is defined %} +{% for range in pool.ranges %} + range {{ range.begin }} {{ range.end }}; +{% endfor %} +{% endif %} +{% if pool.allow is defined %} + allow {{ pool.allow }}; +{% endif %} +{% if pool.deny is defined %} + deny {{ pool.deny }}; +{% endif %} + } +{% endfor %} +{% endif %} +} +{% endfor %} +{% if dhcp_hosts is defined %} + +# +# Host declarations +# +{% for host in dhcp_hosts %} +host {{ host.name | replace (" ","_") | replace ("'","_") | replace (":","_") }} { + hardware ethernet {{ host.mac }}; +{% if host.ip is defined %} + fixed-address {{ host.ip }}; +{% endif %} +} +{% endfor %} +{% endif %} +{% if dhcp_pxeboot_server is defined %} + +# +# PXEBoot server settings +# +option arch code 93 = unsigned integer 16; # RFC4578 + +class "pxeclients" { + match if substring (option vendor-class-identifier, 0, 9) = "PXEClient"; + next-server {{ dhcp_pxeboot_server }}; + + if option arch = 00:07 { + filename "pxelinux/bootx64.efi"; + } else { + filename "pxelinux/pxelinux.0"; + } +} + +{% endif %} diff --git a/roles/dhcp/vars/Alpine.yml b/roles/dhcp/vars/Alpine.yml new file mode 100644 index 0000000..6af3c6f --- /dev/null +++ b/roles/dhcp/vars/Alpine.yml @@ -0,0 +1,12 @@ +# roles/dhcp/vars/Alpine.yml +--- + +dhcp_packages: + - dhcp + +dhcp_config_dir: /etc/dhcp + +dhcp_config: /etc/dhcp/dhcpd.conf + +dhcp_service: dhcpd + diff --git a/roles/dhcp/vars/Debian.yml b/roles/dhcp/vars/Debian.yml new file mode 100644 index 0000000..7a5eb6f --- /dev/null +++ b/roles/dhcp/vars/Debian.yml @@ -0,0 +1,13 @@ +# roles/dhcp/vars/Debian.yml +--- + +dhcp_packages: + - isc-dhcp-server + +dhcp_config_dir: /etc/dhcp + +dhcp_config: /etc/dhcp/dhcpd.conf + +dhcp_service: isc-dhcp-server + +dhcp_apparmor_policy: /etc/apparmor.d/usr.sbin.dhcpd diff --git a/roles/dhcp/vars/RedHat.yml b/roles/dhcp/vars/RedHat.yml new file mode 100644 index 0000000..b14ebf7 --- /dev/null +++ b/roles/dhcp/vars/RedHat.yml @@ -0,0 +1,11 @@ +# roles/dhcp/vars/RedHat.yml +--- + +dhcp_packages: + - "{{ ( ansible_distribution_major_version == '8' ) | ternary( 'dhcp-server', 'dhcp' ) }}" + +dhcp_config_dir: /etc/dhcp + +dhcp_config: /etc/dhcp/dhcpd.conf + +dhcp_service: dhcpd diff --git a/roles/netplan-static-ip/defaults/main.yml b/roles/netplan-static-ip/defaults/main.yml new file mode 100644 index 0000000..c8edb03 --- /dev/null +++ b/roles/netplan-static-ip/defaults/main.yml @@ -0,0 +1,5 @@ +# vim:ft=ansible: +netplan_gateway: 192.168.1.1 +# Note: this is a string representing a YAML array +# NOT a YAML array +netplan_addresses: "[ 192.168.103.100, 192.168.103.101 ]" diff --git a/roles/netplan-static-ip/tasks/main.yml b/roles/netplan-static-ip/tasks/main.yml new file mode 100644 index 0000000..e0a43d3 --- /dev/null +++ b/roles/netplan-static-ip/tasks/main.yml @@ -0,0 +1,3 @@ +#!/usr/bin/env ansible-playbook +# vim:ft=ansible: + diff --git a/roles/requirements.yml b/roles/requirements.yml index 2175ff4..c8a97c8 100644 --- a/roles/requirements.yml +++ b/roles/requirements.yml @@ -5,6 +5,11 @@ # MAD PROPS to geerlingguy; if for some reason you end up reading this, hit me # up and I'll buy you a beer or a pizza or something. +# Upstream: https://github.com/bertvv/ansible-role-dhcp +- src: bertvv.dhcp + version: master + name: dhcp + # Orchestration # Upstream: https://github.com/geerlingguy/ansible-role-docker - src: geerlingguy.docker