diff --git a/defaults/main.yml b/defaults/main.yml deleted file mode 100644 index d5d4057..0000000 --- a/defaults/main.yml +++ /dev/null @@ -1,25 +0,0 @@ ---- -sg_common_int_if: bge0 -sg_common_int_ip: - -sg_common_ext_if: fxp0 -sg_common_ext_ip: -sg_common_ext_dns: - -sg_common_domain: domain.tld -sg_common_hostname: simple-gateway - -# sg_common_staticHosts: -# example: -# mac: 00:00:fa:ce:b0:0c -# addr: 10.0.1.127 -# name: foo - -# stubZones: -# example: -# name: foo -# private: true -# localZone: 16.172.in-addr.arpa -# addresses: -# - 172.16.0.1 -# - 172.16.0.2 diff --git a/files/pkg.conf b/files/pkg.conf deleted file mode 100644 index 6f8b489..0000000 --- a/files/pkg.conf +++ /dev/null @@ -1 +0,0 @@ -installpath = http://mirror.esc7.net/%m/ diff --git a/files/rc.local b/files/rc.local new file mode 100644 index 0000000..365a694 --- /dev/null +++ b/files/rc.local @@ -0,0 +1 @@ +cp /etc/resolv.conf.final /etc/resolv.conf diff --git a/files/sshd_banner b/files/sshd_banner new file mode 100644 index 0000000..3800cab --- /dev/null +++ b/files/sshd_banner @@ -0,0 +1 @@ +This system is for authorized use only! diff --git a/files/sysctl.conf b/files/sysctl.conf index 1e3be19..a4a09a4 100644 --- a/files/sysctl.conf +++ b/files/sysctl.conf @@ -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 diff --git a/handlers/main.yml b/handlers/main.yml index f03752b..7bb97cf 100644 --- a/handlers/main.yml +++ b/handlers/main.yml @@ -22,3 +22,6 @@ - name: pf command: pfctl -f /etc/pf.conf + +- name: sshd + command: /etc/rc.d/sshd restart diff --git a/tasks/main.yml b/tasks/main.yml index 5e6eba7..ca24003 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -1,5 +1,5 @@ --- -- name: Configure interfaces +- name: Configure Interfaces template: src: hostname.interface.j2 dest: /etc/hostname.{{ item.name }} @@ -8,7 +8,7 @@ mode: 0640 notify: - netstart - with_items: "{{ sg_network }}" + with_items: "{{ sg_interfaces }}" loop_control: label: "{{ item.name }}" @@ -48,10 +48,51 @@ 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 @@ -59,3 +100,13 @@ 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 + diff --git a/templates/hostname.interface.j2 b/templates/hostname.interface.j2 index fb4cba2..19a92b3 100644 --- a/templates/hostname.interface.j2 +++ b/templates/hostname.interface.j2 @@ -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 %} diff --git a/templates/hosts.j2 b/templates/hosts.j2 new file mode 100644 index 0000000..6c26ca9 --- /dev/null +++ b/templates/hosts.j2 @@ -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 %} diff --git a/templates/mygate.j2 b/templates/mygate.j2 new file mode 100644 index 0000000..e7e5d22 --- /dev/null +++ b/templates/mygate.j2 @@ -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') }} diff --git a/templates/myname.j2 b/templates/myname.j2 index e3dffb8..f7ee38b 100644 --- a/templates/myname.j2 +++ b/templates/myname.j2 @@ -1 +1 @@ -{{ sg_globalconf.hostname }}.{{ sg_globalconf.domain }} +{{ sg_globalconf.hostname }} diff --git a/templates/pf.base.j2 b/templates/pf.base.j2 index 051f0a2..2dedb87 100644 --- a/templates/pf.base.j2 +++ b/templates/pf.base.j2 @@ -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 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 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 }} @@ -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 persist +table persist +pass in on {{ filter_interface }} from {% 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 diff --git a/templates/pkg.conf.j2 b/templates/pkg.conf.j2 new file mode 100644 index 0000000..f1d5a02 --- /dev/null +++ b/templates/pkg.conf.j2 @@ -0,0 +1,2 @@ +installpath = {{ sg_pkg_path }} + diff --git a/templates/resolv.conf.boot.j2 b/templates/resolv.conf.boot.j2 new file mode 100644 index 0000000..5a81883 --- /dev/null +++ b/templates/resolv.conf.boot.j2 @@ -0,0 +1,4 @@ +lookup file bind +{% for resolver in sg_globalconf.bootresolvers %} +nameserver {{ resolver }} +{% endfor %} diff --git a/templates/resolv.conf.final.j2 b/templates/resolv.conf.final.j2 new file mode 100644 index 0000000..4d5d83b --- /dev/null +++ b/templates/resolv.conf.final.j2 @@ -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 }} diff --git a/templates/sshd_config.j2 b/templates/sshd_config.j2 new file mode 100644 index 0000000..6fbf81d --- /dev/null +++ b/templates/sshd_config.j2 @@ -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