Skip to content

Commit

Permalink
Configuration Refactor
Browse files Browse the repository at this point in the history
This commit refactors the entire sg-base role to use the newest
configuration refactor.  This refactor includes exciting new features
such as CARP, cleaner ways to specify multiple networks, and more!

The most notable change with this commit is that the gateway now
implements fairly advanced firewalling based on what services are
loaded.  Ideally in a future version this will be factored out to the
individual roles, but right now the template logic to do so would make
this commit too complex to review.

This refactor includes significant work from @m-wynn.
  • Loading branch information
the-maldridge committed Mar 24, 2017
1 parent 534c8f8 commit bfaa814
Show file tree
Hide file tree
Showing 16 changed files with 247 additions and 50 deletions.
25 changes: 0 additions & 25 deletions defaults/main.yml

This file was deleted.

1 change: 0 additions & 1 deletion files/pkg.conf

This file was deleted.

1 change: 1 addition & 0 deletions files/rc.local
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
cp /etc/resolv.conf.final /etc/resolv.conf
1 change: 1 addition & 0 deletions files/sshd_banner
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This system is for authorized use only!
1 change: 1 addition & 0 deletions files/sysctl.conf
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
# the many available variables.

ddb.panic=0 # 0=Do not drop into ddb on a kernel panic
net.inet.carp.preempt=1 # 1=Enable carp(4) preemption
net.inet.ip.forwarding=1 # 1=Permit forwarding (routing) of IPv4 packets
3 changes: 3 additions & 0 deletions handlers/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,6 @@

- name: pf
command: pfctl -f /etc/pf.conf

- name: sshd
command: /etc/rc.d/sshd restart
57 changes: 54 additions & 3 deletions tasks/main.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
- name: Configure interfaces
- name: Configure Interfaces
template:
src: hostname.interface.j2
dest: /etc/hostname.{{ item.name }}
Expand All @@ -8,7 +8,7 @@
mode: 0640
notify:
- netstart
with_items: "{{ sg_network }}"
with_items: "{{ sg_interfaces }}"
loop_control:
label: "{{ item.name }}"

Expand Down Expand Up @@ -48,14 +48,65 @@
group: wheel
mode: 0644
with_items:
- pkg.conf
- rc.securelevel
- sysctl.conf

- name: Configure SSH
template:
src: sshd_config.j2
dest: /etc/ssh/sshd_config
owner: root
group: wheel
mode: 0644
notify:
- sshd

- name: Copy SSH banner
copy:
src: "{{ sshd_banner | default('sshd_banner') }}"
dest: /etc/ssh/sshd_banner
owner: root
group: wheel
mode: 0644
notify:
- sshd

- name: Configure package system
template:
src: pkg.conf.j2
dest: /etc/pkg.conf
owner: root
group: wheel
mode: 0644

- name: Template Startup Control Files
template:
src: "{{ item }}.j2"
dest: /etc/{{ item }}
owner: root
group: wheel
mode: 0644
with_items:
- hosts
- myname
- mygate
- resolv.conf.boot
- resolv.conf.final

- name: Create rc.conf.local.d
file:
path: /etc/rc.conf.local.d
state: directory
owner: root
group: wheel
mode: 0644

- name: Create rc.local
copy:
src: rc.local
dest: /etc/rc.local
owner: root
group: wheel
mode: 0644
when: sg_networks|selectattr('services', 'issuperset', ['dhcp'])|list

23 changes: 20 additions & 3 deletions templates/hostname.interface.j2
Original file line number Diff line number Diff line change
@@ -1,7 +1,24 @@
{% if item.name == sg_interfaces[0].name %}
{# This handles the case where we boot faster that the upstream resolver #}
!cp /etc/resolv.conf.boot /etc/resolv.conf
{% endif %}
{% if item.syncdev is defined %}
up syncdev {{ item.syncdev }}
{% else %}
{% if item.address is defined %}
{% if item.carpdev is defined %}
inet {{ item.address | ipaddr('address') }} {{ item.address | ipaddr('netmask') }} NONE vhid {{ item.vhid }} carpdev {{ item.carpdev }} advskew {{ item.advskew }}
{% else %}
{% if item.address | ipaddr %}
inet {{ item.address }} {{ item.cidr | ipaddr('netmask') }} NONE
inet {{ item.address | ipaddr('address') }} {{ item.address | ipaddr('netmask') }} NONE
{% if item.aliases is defined %}
{% for alias in item.aliases %}
inet alias {{ alias | ipaddr('address') }} {{ alias | ipaddr('netmask') }}
{% endfor %}
{% endif %}
{% elif item.address == "dhcp" %}
dhcp
{% endif %}
-inet6
up
{% endif %}
{% endif %}
{% endif %}
13 changes: 13 additions & 0 deletions templates/hosts.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
127.0.0.1 localhost
::1 localhost

{# This might seem like an epic kludge, but really this allows us to
absolutely know that pf will come up even if the external boot
resolver is absolutely dead. This list must contain all symbolic
names that are used in the pf rules for hosts managed by this
router. #}
{% if sg_globalconf.boothosts is defined %}
{% for host in sg_globalconf.boothosts %}
{{ host.ip }} {{ host.name }}
{% endfor %}
{% endif %}
2 changes: 2 additions & 0 deletions templates/mygate.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{# This defaults to localhost to account for if dhcp is used for egress #}
{{ sg_globalconf.default_gateway | default('127.0.0.1') }}
2 changes: 1 addition & 1 deletion templates/myname.j2
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{{ sg_globalconf.hostname }}.{{ sg_globalconf.domain }}
{{ sg_globalconf.hostname }}
145 changes: 128 additions & 17 deletions templates/pf.base.j2
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
# Inferface Listing
{% for interface in sg_network %}
{% for interface in sg_interfaces %}
# {{ interface.name }}: {{ interface.desc }}
{% endfor %}
# End Interface Listing

# Addresses that should never be seen on global networks
table <martians> const {0.0.0.0/8 100.64.0.0/10 127.0.0.0/8 169.254.0.0/16 172.16.0.0/12 192.0.2.0/24 192.168.0.0/16 !192.168.42.0/24 198.18.0.0/15 198.51.100.0/24 203.0.113.0/24 240.0.0.0/4}
table <martians> const {0.0.0.0/8 100.64.0.0/10 127.0.0.0/8 169.254.0.0/16 172.16.0.0/12 192.0.2.0/24 192.168.0.0/16 198.18.0.0/15 198.51.100.0/24 203.0.113.0/24 240.0.0.0/4 {% for network in sg_networks %}!{{ network.cidr }} {% endfor %}}

# Setup the skips for 'special' interfaces
{% for interface in sg_interfaces %}
{% if interface.syncdev is defined %}
# This is the backing syncdev for {{ interface.name }}
set skip on {{ interface.syncdev }}
{% endif %}
{% endfor %}

# Configure default policies
set block-policy {{ sg_globalconf.pf.block_policy }}
Expand All @@ -15,27 +23,130 @@ block log
# Permit ping of the gateway
pass proto icmp

{% for interface in sg_network %}
#==============================================================================
# Configuration for {{ interface.name }}
# {{ interface.desc }}
#==============================================================================
{% if "ssh" in interface.services %}
# Permit SSH
pass in on {{ interface.name }} inet proto tcp to any port ssh
pass out on {{ interface.name }} inet proto tcp to any port ssh
{% for interface in sg_interfaces|json_query('[].carpdev')|list|unique %}
# Set pass on CARP interfaces
pass in on {{ interface }} proto carp keep state (no-sync)
pass out on {{ interface }} proto carp keep state (no-sync)
{% endfor %}

pass out on egress proto tcp to {{ sg_pkg_server }} port {80 443}

{% if sg_globalconf.ssh is defined and sg_globalconf.ssh.listen_egress is defined %}
{% if sg_globalconf.ssh.listen_egress %}
pass in on egress proto tcp to any port ssh

{% endif %}
{% endif %}
{# The blank line is in the if case above to prevent a double space #}
{% for network in sg_networks %}
{% if network.services or network.hosts %}
{# Its really handy to know this interface later, so we just grab it here #}
{% set interface = sg_interfaces|selectattr('name', 'equalto', network.attach_to)|first %}
{% if interface.carpdev is defined %}
{# OpenBSD recommends only filtering on the real interface #}
{% set filter_interface = interface.carpdev %}
{% else %}
{% set filter_interface = interface.name %}
{% endif %}
#===============================================================================
# Network: {{ network.name }}
# Description: {{ network.desc }}
#===============================================================================
{% endif %}
{# First we setup the services, then we setup the NAT rules, then rdr rules #}
{% if 'ssh' in network.services %}
# The router will accept ssh on this network.
# The implication is that devices on this network are trusted.
pass in on {{ filter_interface }} inet proto tcp to {{ filter_interface }} port ssh
{% if 'jumphost' in network.services %}
pass out on {{ filter_interface }} inet proto tcp to any port ssh
{% endif %}

{% endif %}
{% if 'ntp' in network.services %}
# Local NTP is provided on this network.
pass in on {{ filter_interface }} inet proto tcp to {{ interface.name }} port ntp

{% endif %}
{% if 'dns' in network.services %}
# DNS is offered on this network.
pass in on {{ filter_interface }} inet proto {tcp udp} to {{ interface.name }} port domain
pass out on egress inet proto {tcp udp} from egress to any port domain

{% endif %}
{% if 'nat' in network.services %}
{% set nat_out = 'egress' %}
{# first we need to see if there's a nat_to directive in this network's
serviceconf, if there is we will nat_to that. Otherwise we'll nat-to the all
adapters in the egress group using the round-robin mode. #}
{% if network.serviceconf.nat is defined and network.serviceconf.nat.nat_to is defined %}
{% set nat_to = network.serviceconf.nat.nat_to %}
{# Now we need to flatten out carp devices since we can only do filtering on
the real routing interfaces #}
{% set nat_out = sg_interfaces|selectattr('name', 'equalto', nat_to)|first %}
{% if nat_out.carpdev is defined %}
{% set nat_out = nat_out.carpdev %}
{% else %}
{% set nat_out = nat_out.name %}
{% endif %}
{% else %}
{% set nat_to = nat_out %}
{% endif %}
# NAT out traffic to {{ nat_to }}
match out on {{ nat_out }} inet from {{ filter_interface }}:network to any nat-to ({{ nat_to }})

# Once a packet has been passed in, it still needs to be passed out.
pass out on {{ nat_out }}

# This sets the 'next action' for packets that were passed in on
# egress, in this case they were almost certainly destined for an
# internal host.
pass out on {{ filter_interface }} received-on egress
{% if network.serviceconf.nat.mode == 'dhcp' %}
table <activedhcp> persist
table <baddhcp> persist
pass in on {{ filter_interface }} from <activedhcp>
{% endif %}
{% if network.serviceconf.nat.autonat is defined %}
{% for cidr in network.serviceconf.nat.autonat %}
pass in on {{ filter_interface }} from {{ cidr }}
{% endfor %}

{% if "nat" in interface.services %}
# Use NAT
match out on egress inet from {{ interface.name }}:network to any nat-to (egress:0)
{% endif %}
{% if network.hosts is defined %}
{# TODO: only print this out if at least one host has firewall rules #}
#-------------------------------------------------------------------------------
# Per Host Rules
#-------------------------------------------------------------------------------

# Permit traffic
pass in on {{ interface.name }}
pass out on {{ interface.name }}
{% for host in network.hosts %}
{% if host.pf is defined %}
{% if host.desc is defined %}
# {{ host.desc }}
{% if host.pf.in is defined %}
{% for rule in host.pf.in %}
pass in on egress proto {{ rule.proto }} from {{ rule.src }} to {{ rule.to|default('any') }} port {{ rule.port }} rdr-to {{ host.addr }}{% if rule.dest_port is defined %} port {{ rule.dest_port }}
{% else %}

{% endif %}
{% endfor %}
{% endif %}
{% endif %}
{% if host.pf.out is defined %}
{% for rule in host.pf.out %}
pass in on {{ filter_interface }} proto {{ rule.proto }} from {{ host.addr }} to {{ rule.dest }}{% if rule.port != 'any' %} port {{ rule.port }}
{% endif %}
{% endfor %}
{% endif %}
{# The following blank line seperates multiple hosts #}

{% endif %}
{% endfor %}
{% endif %}
{% endif %}
{% endfor %}
{# The following blank line seperates multiple networks #}

# IPv6
block inet6

Expand Down
2 changes: 2 additions & 0 deletions templates/pkg.conf.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
installpath = {{ sg_pkg_path }}

4 changes: 4 additions & 0 deletions templates/resolv.conf.boot.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
lookup file bind
{% for resolver in sg_globalconf.bootresolvers %}
nameserver {{ resolver }}
{% endfor %}
7 changes: 7 additions & 0 deletions templates/resolv.conf.final.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
lookup file bind
{# Abandon all hope ye who enter here #}
{# This gets the address of the first adapter that has dns running on it #}
{% set net = sg_networks|selectattr('services', 'issuperset', ['dns'])|list|first %}
{% set interface = sg_interfaces|selectattr('name', 'equalto', net.attach_to) %}
{% set address = interface|map(attribute='address')|ipaddr('address')|first %}
nameserver {{ address }}
10 changes: 10 additions & 0 deletions templates/sshd_config.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
PermitRootLogin no

AuthorizedKeysFile .ssh/authorized_keys
{% if sg_globalconf.ssh is defined and sg_globalconf.ssh.permit_password is defined %}
PasswordAuthentication {{ sg_globalconf.ssh.permit_password | ternary('yes', 'no') }}
{% endif %}

Banner /etc/ssh/sshd_banner

Subsystem sftp /usr/libexec/sftp-server

0 comments on commit bfaa814

Please sign in to comment.