440 Configure DHCP

Use case

Use the role vbotka.freebsd.dhcp to configure DHCP. Use the role vbotka.freebsd.pf to configure pf.

Tree

shell> tree .
.
├── ansible.cfg
├── host_vars
│   └── iocage_05
│       ├── dhcp.yml
│       └── pf.yml
├── iocage.ini
├── pb-dhcp.yml
└── pb-pf.yml

Synopsis

  • The Ansible controller connects the iocage host iocage_05 at the wlan interface configured in /etc/rc.conf

    gateway_enable="YES"
    defaultrouter="10.10.0.1"
    cloned_interfaces="bridge0"
    ifconfig_bridge0="inet 10.1.0.1/24"
    wlans_iwm0="wlan0"
    create_args_wlan0="country US"
    ifconfig_wlan0="WPA SYNCDHCP"
    
  • In the playbook pb-dhcp.yml at iocage_05 configure:

    • subnet 10.1.0.0/24

    • range 100-200

    • dhcpd_ifaces=”bridge0”

  • In the playbook pb-pf.yml at iocage_05 configure:

    • blacklistd, fail2ban, and sshguard

    • nat

    • log all blocked

    • allow SSH from the external network

    • pass from localnet and jails’ vnet to any

Requirements

  • root privilege in the managed nodes.

Notes

TBD

Note

vbotka.freebsd.dhcp is the role dhcp in the collection vbotka.freebsd.
vbotka.freebsd_dhcp is the role freebsd_dhcp in the namespace vbotka.
vbotka.freebsd.pf is the role pf in the collection vbotka.freebsd.
vbotka.freebsd_pf is the role freebsd_pf in the namespace vbotka.
Please make sure the versions are the same before you switch between them.

See also

ansible.cfg

[defaults]
gathering = explicit
callback_result_format = yaml
display_skipped_hosts = false

[connection]
pipelining = true

Inventory iocage.ini

iocage_05 ansible_host=handy

[iocage]
iocage_05

[iocage:vars]
ansible_user=admin
ansible_become=true
ansible_python_interpreter=auto_silent

host_vars

host_vars/iocage_05/dhcp.yml
---
bsd_dhcpd_install: false

bsd_dhcpd_enable: true
bsd_dhcpd_debug: false
bsd_dhcpd_sanity: true
bsd_dhcpd_sanity_fatal: true
bsd_dhcpd_backup_conf: true

# rc.conf
bsd_dhcpd_rcconf:
  - name: dhcpd_ifaces
    value: bridge0

# Template
bsd_dhcpd_template: dhcpd.conf-minimal.j2

# Gobal
bsd_dhcpd_domain_name: g3.example.com
bsd_dhcpd_domain_name_servers: [8.8.8.8, 8.8.4.4]
bsd_dhcpd_default_lease_time: 600
bsd_dhcpd_max_lease_time: 7200

# Primary server
bsd_dhcpd_authoritative: true

# Subnet
bsd_dhcpd_subnet: 10.1.0
bsd_dhcpd_netmask: 255.255.255.0
bsd_dhcpd_subnet_from: 100
bsd_dhcpd_subnet_to: 200
bsd_dhcpd_routers: [10.1.0.1]
bsd_dhcpd_subnet_broadcast: 255

# EOF
host_vars/iocage_05/pf.yml
---
pf_install: false

pf_enable: true
pf_blacklistd: true
pf_fail2ban: true
pf_relayd: false
pf_sshguard: true

pf_blacklistd_enable: true
pf_fail2ban_enable: true
pf_sshguard_enable: true

pf_debug: false
pf_backup_conf: true

pfconf_only: false
pfconf_validate: true

# /etc/pf.conf
pf_type: default2
pf_log_enable: true

pf_ext_if: wlan0
pf_int_if: bridge0
pf_log_all_blocked: log
pf_pass_icmp_types: [echoreq, unreach]
pf_pass_icmp6_types: [echoreq, unreach]
pf_local_net: 10.10.1.0/24
pf_local_vnet: 10.1.0.0/24
pf_tcp_services: [ssh]

pf_macros:
  ext_if: "{{ pf_ext_if }}"
  int_if: "{{ pf_int_if }}"
  localnet: "{{ pf_local_net }}"
  localvnet: "{{ pf_local_vnet }}"
  logall: "{{ pf_log_all_blocked }}"
  icmp_types: "{{ pf_pass_icmp_types }}"
  icmp6_types: "{{ pf_pass_icmp6_types }}"
  tcp_services: "{{ pf_tcp_services }}"

# pf blocks
pf_options:
  - set skip on lo0
  - set block-policy return
  - set loginterface $ext_if

pf_tables:
  - table <sshabuse> persist

pf_normalization:
  - scrub in on $ext_if all fragment reassemble

pf_translation:
  - "{{ pf_rules_rdr }}"
  - nat on $ext_if from $localnet to any -> ($ext_if)
  - nat on $ext_if from $localvnet to any -> ($ext_if)

pf_filtering:
  - antispoof for $ext_if
  - "{{ pf_anchors }}"
  - block $logall all
  - pass in on $ext_if proto tcp to any port $tcp_services flags S/SA keep state
  - pass inet proto icmp icmp-type $icmp_types
  - pass inet6 proto icmp6 icmp6-type $icmp6_types
  - pass from { self, $localnet, $localvnet } to any keep state

# blacklistd
pf_blacklistd_conf_remote: []
pf_blacklistd_conf_local:
  - {adr: ssh, type: stream, proto: '*', owner: '*', name: '*', nfail: '3', disable: 24h}
  - {adr: ftp, type: stream, proto: '*', owner: '*', name: '*', nfail: '3', disable: 24h}
  - {adr: smtp, type: stream, proto: '*', owner: '*', name: '*', nfail: '3', disable: 24h}
  - {adr: smtps, type: stream, proto: '*', owner: '*', name: '*', nfail: '3', disable: 24h}
  - {adr: submission, type: stream, proto: '*', owner: '*', name: '*', nfail: '3', disable: 24h}
  - {adr: '*', type: '*', proto: '*', owner: '*', name: '*', nfail: '3', disable: '60'}
pf_blacklistd_flags: '-r'
pf_blacklistd_rcconf:
  - {name: blacklistd_flags, value: "{{ pf_blacklistd_flags }}"}

# EOF

Playbook pb-dhcp.yml

- name: Test role vbotka.freebsd.dhcp
  hosts: iocage
  gather_facts: true

  roles:
    - vbotka.freebsd.dhcp

Playbook output - Install packages

(env) > ansible-playbook pb-dhcp.yml -i iocage.ini -t bsd_dhcpd_packages -e bsd_dhcpd_install=true
PLAY [Test role vbotka.freebsd.dhcp] *******************************************

TASK [Gathering Facts] *********************************************************
ok: [iocage_05]

TASK [vbotka.freebsd.dhcp : Packages: Install dhcp packages.] ******************
ok: [iocage_05] => (item=net/isc-dhcp44-server)

PLAY RECAP *********************************************************************
iocage_05                  : ok=2    changed=0    unreachable=0    failed=0    skipped=5    rescued=0    ignored=0   

Playbook output - Configure DHCP

(env) > ansible-playbook pb-dhcp.yml -i iocage.ini
PLAY [Test role vbotka.freebsd.dhcp] *******************************************

TASK [Gathering Facts] *********************************************************
ok: [iocage_05]

TASK [vbotka.freebsd.dhcp : Sanity: Collect installed packages.] ***************
ok: [iocage_05]

TASK [vbotka.freebsd.dhcp : Sanity: Set my_missing_packages] *******************
ok: [iocage_05]

TASK [vbotka.freebsd.dhcp : Rcconf: Enable and start dhcpd] ********************
ok: [iocage_05]

TASK [vbotka.freebsd.dhcp : Rcconf: Configure dhcpd.] **************************
ok: [iocage_05] => (item={'name': 'dhcpd_ifaces', 'value': 'bridge0'})

TASK [vbotka.freebsd.dhcp : Conf: Configure /usr/local/etc/dhcpd.conf from dhcpd.conf-minimal.j2] ***
ok: [iocage_05]

PLAY RECAP *********************************************************************
iocage_05                  : ok=6    changed=0    unreachable=0    failed=0    skipped=6    rescued=0    ignored=0   

Playbook pb-pf.yml

- name: Test role vbotka.freebsd.pf
  hosts: iocage
  gather_facts: true

  roles:
    - vbotka.freebsd.pf

Playbook output - Install packages

(env) > ansible-playbook pb-pf.yml -i iocage.ini -t pf_packages -e pf_install=true
PLAY [Test role vbotka.freebsd.pf] *********************************************

TASK [Gathering Facts] *********************************************************
ok: [iocage_05]

TASK [vbotka.freebsd.pf : Packages: Install packages.] *************************
ok: [iocage_05]

PLAY RECAP *********************************************************************
iocage_05                  : ok=2    changed=0    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0   

Playbook output - Configure pf

Firewall starting and restarting breaks the ssh connections. See the handlers for details. As a consequence, both handlers starting and reloading don’t work properly and the ssh connection will stale. Therefore, let us first configure the rules

(env) > ansible-playbook pb-pf.yml -i iocage.ini -e pf_enable=false
PLAY [Test role vbotka.freebsd.pf] *********************************************

TASK [Gathering Facts] *********************************************************
ok: [iocage_05]

TASK [vbotka.freebsd.pf : Rcconf-blacklistd: Stat /etc/rc.d/blacklistd] ********
ok: [iocage_05]

TASK [vbotka.freebsd.pf : Blacklistd: Backup /etc/blacklistd.conf.orig] ********
ok: [iocage_05]

TASK [vbotka.freebsd.pf : Blacklistd: Configure /etc/blacklistd.conf] **********
ok: [iocage_05]

TASK [vbotka.freebsd.pf : Rcconf-fail2ban: Stat /usr/local/etc/rc.d/fail2ban] ***
ok: [iocage_05]

TASK [vbotka.freebsd.pf : Fail2ban: Configure /usr/local/etc/fail2ban/fail2ban.local] ***
ok: [iocage_05]

TASK [vbotka.freebsd.pf : Fail2ban: Configure /usr/local/etc/fail2ban/jail.local] ***
ok: [iocage_05]

TASK [vbotka.freebsd.pf : Rcconf-sshguard: Stat /usr/local/etc/rc.d/sshguard] ***
ok: [iocage_05]

TASK [vbotka.freebsd.pf : Configure sshguard.conf] *****************************
ok: [iocage_05] => (item={'key': 'BACKEND', 'value': '/usr/local/libexec/sshg-fw-pf'})
ok: [iocage_05] => (item={'key': 'WHITELIST_FILE', 'value': '/usr/local/etc/sshguard.whitelist'})

TASK [vbotka.freebsd.pf : Pfconf: Configure and validate rules using template default2-pf.conf.j2] ***
ok: [iocage_05]

TASK [vbotka.freebsd.pf : Rcconf-blacklistd: Enable and start blacklistd.] *****
ok: [iocage_05]

TASK [vbotka.freebsd.pf : Rcconf-blacklistd: Configure blacklistd.] ************
ok: [iocage_05] => (item={'name': 'blacklistd_flags', 'value': '-r'})

TASK [vbotka.freebsd.pf : Rcconf-fail2ban: Enable and start fail2ban.] *********
ok: [iocage_05]

TASK [vbotka.freebsd.pf : Rcconf-fail2ban: Configure fail2ban.] ****************
ok: [iocage_05] => (item={'name': 'fail2ban_flags', 'value': ''})

TASK [vbotka.freebsd.pf : Rcconf-sshguard: Enable and start sshguard.] *********
ok: [iocage_05]

TASK [vbotka.freebsd.pf : Rcconf-pf: Disable and stop pf.] *********************
changed: [iocage_05]

TASK [vbotka.freebsd.pf : Rcconf-pflog: Enable and start pflog.] ***************
ok: [iocage_05]

RUNNING HANDLER [vbotka.freebsd.pf : Disable and stop pf] **********************
changed: [iocage_05]

PLAY RECAP *********************************************************************
iocage_05                  : ok=18   changed=2    unreachable=0    failed=0    skipped=39   rescued=0    ignored=0   

Playbook output - Enable pf

(env) > ansible-playbook pb-pf.yml -i iocage.ini -t pf_rcconf_pf
PLAY [Test role vbotka.freebsd.pf] *********************************************

TASK [Gathering Facts] *********************************************************
ok: [iocage_05]

TASK [vbotka.freebsd.pf : Rcconf-pf: Enable and start pf.] *********************
changed: [iocage_05]

RUNNING HANDLER [vbotka.freebsd.pf : Start pf] *********************************
changed: [iocage_05]

PLAY RECAP *********************************************************************
iocage_05                  : ok=3    changed=2    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0   

Result

isc-dhcpd status

(env) > ssh admin@handy sudo service isc-dhcpd status
dhcpd is running as pid 4208.

/usr/local/etc/dhcpd.conf

(env) > ssh admin@handy cat /usr/local/etc/dhcpd.conf
# Ansible managed

# Global options
option domain-name "g3.example.com";
option domain-name-servers 8.8.8.8, 8.8.4.4;
default-lease-time 600;
max-lease-time 7200;

# Denotes this server as the primary DHCP server for the local network
authoritative;

# Internal subnet configuration
subnet 10.1.0.0 netmask 255.255.255.0 {
  range 10.1.0.100 10.1.0.200;
  option routers 10.1.0.1;
  option broadcast-address 10.1.0.255;
}

pf status

(env) > ssh admin@handy sudo service pf status
Status: Disabled for 0 days 00:00:03          Debug: Urgent

Interface Stats for wlan0             IPv4             IPv6
  Bytes In                               0                0
  Bytes Out                              0                0
  Packets In
    Passed                            2705                0
    Blocked                            391                0
  Packets Out
    Passed                               0                0
    Blocked                           1501                0

State Table                          Total             Rate
  current entries                       28               
  searches                            4722         1574.0/s
  inserts                               53           17.7/s
  removals                              25            8.3/s
Counters
  match                                514          171.3/s
  bad-offset                             0            0.0/s
  fragment                               0            0.0/s
  short                                  0            0.0/s
  normalize                              0            0.0/s
  memory                                 0            0.0/s
  bad-timestamp                          0            0.0/s
  congestion                             0            0.0/s
  ip-option                              0            0.0/s
  proto-cksum                            0            0.0/s
  state-mismatch                         0            0.0/s
  state-insert                           0            0.0/s
  state-limit                            0            0.0/s
  src-limit                              0            0.0/s
  synproxy                               0            0.0/s
  map-failed                             0            0.0/s
  translate                              0            0.0/s

/etc/pf.conf

(env) > ssh admin@handy cat /etc/pf.conf
# Ansible managed
# template: default2-pf.conf.j2

# MACROS
ext_if = "wlan0"
int_if = "bridge0"
localnet = "10.10.1.0/24"
localvnet = "10.1.0.0/24"
logall = "log"
icmp_types = "{ echoreq, unreach }"
icmp6_types = "{ echoreq, unreach }"
tcp_services = "{ ssh }"

# TABLES
table <sshabuse> persist

# OPTIONS
set skip on lo0
set block-policy return
set loginterface $ext_if

# NORMALIZATION
scrub in on $ext_if all fragment reassemble

# TRANSLATION
nat on $ext_if from $localnet to any -> ($ext_if)
nat on $ext_if from $localvnet to any -> ($ext_if)

# FILTERING
antispoof for $ext_if
anchor "blacklistd/*" in on $ext_if
anchor "f2b/*"
block $logall all
pass in on $ext_if proto tcp to any port $tcp_services flags S/SA keep state
pass inet proto icmp icmp-type $icmp_types
pass inet6 proto icmp6 icmp6-type $icmp6_types
pass from { self, $localnet, $localvnet } to any keep state

# EOF