Securing & segregating your home networks

Hi dear community.

Even if I’ve been a cybersecurity specialist (red & blue teamer) which doesn’t grant me any authority on the topic, even less so because I’m no longer professionaly on the tech playground, but let’s say I’m sensitive and experienced on the subject.

This guide isn’t about your physical HA security. (<TL/DR> Reliable power supply + UPS, run it on an SSD and not an SDcard, put it in a safe place and in a hard case). This post requires some Linux / network / security basic understanding since it could confuse untrained users. OpenWRT or Opnsense won’t get you as far as this setup, but are much more accessible for beginners.

[EDIT 13/05/2024]: I updated this howto to replace Knocked by Tailscale. It’s so rich, versatile and secure and became a comfortable replacement for Knockd.

Table of content

1/ Goals of the project
2/ Bill of Materials (BoM)
3/ Setting up your firewall box
4/ Connexion redundancy, Tailscale & CrowdSec
5/ Configuring your switch, Wifi and separating your networks
6/ Securing your HA

1. Goals of the project

We all have different goals and sensibilities, but most HA users like privacy and cloud independence.
We just want their hardware, we don’t need their apps since we want to control everything through HA.

Why no cloud dependency? When a provider imposes its (average) API and forces you to use their app, you’re at risk that:

  • They are down for maintenance
  • They are down because they were hacked
  • They will lose your private data/credentials (not if, when)
  • That some attacks can be carried to your home through their hardware, API or services

→ I wish for my security to rely as little as possible on 3rd parties and be able to control them when I’ve no choice.

No connected microphone: Also, no mic in my home. No alexa, siri, google or whatever else. Voice activation is cute, but is mainly fulfilling 3 letter agencies’ wet dreams of having people install connected microphones in their homes and at their expense…
Besides I 100% prefer an APP to a voice activation.

Here is what we’ll achieve:

  • Solid Ingress filtering (from the Internet toward my LANs)
  • Separated networks for IoT devices
  • Guests isolated on a subnet (& their traffic going through a VPN)
  • Less advertisement & trackers
  • Dynamic routing of what goes through regular or VPN connection based on protocol
  • Resilience to a any software or daemon potential vulnerability
  • Robust Wifi security
  • Connexion redundancy
  • Limit IoT communication capacities to the strictly necessary (you really want your cameras exposed to other eyes?)
  • Long lasting, this is a budget, so it should last
  • Real-time notifications if anything suspicious happens
  • user friendliness (Non-user-friendly security usually is a highway to upset users or get them to circumvent it)

2. Bill of Materials (BoM)

We’ll need a bit of hardware if we want to be serious about security. I’ll stick to affordable component. You can swap any of those by another brand or model, but my explanations and screenshot will be made on this exact hardware.

  • 4 hours of your time
  • A “smart” switch, here a Netgear GS324TP ~ $250
  • Serious Wifi Access Point, here Unifi UAP-AC-pro or LR ~ $120 per AP
  • A firewall box, here a Quotom Qotom Mini PC (I5 Core, 8 Gb and 128 Gb SSD i5, 4 Gigabit Ethernet) $233

You’re looking at something between $500 and $1K to get a real, secure, resilient network, but this investment should hold a decade.

3. Setting up your firewall

Take your Qotom box and install a Linux distro. I’d weigh in for a Debian one. It comes with pros & cons, but there are many pros for a “server” context. There are hardened, more secure distros, but you may fight more than necessary here. Install your favorite jed or emacs editor, prompt, ssh key, etc. Not my circus, not my monkeys.

I understand the move for the dynamic naming of ethernet interfaces, but I’m not a big fan.
In your /etc/default/grub, add an extra:

GRUB_CMDLINE_LINUX_DEFAULT="net.ifnames=0 biosdevname=0 ipv6.disable=0 reboot=acpi"

Next, let’s deal with our 4 ethernet interfaces:

  • on eth0, we’ll plug our DMZ
  • on eth1, our Home LAN
  • on eth2.104, our Guests LAN
  • on eth3.105, our IoT devices

.104 means this ethernet interface will be listening to VLAN 104
.105 for VLAN 105, vlans that we’ll define on our smart switch, more about it soon.

In /etc/network/interfaces:

source /etc/network/interfaces.d/*

iface lo inet loopback
auto lo eth0 eth1 eth2.104 eth3.105
allow-hotplug tun0

iface eth0 inet static
  address 192.168.0.1/24
  gateway 192.168.1.2
  dns-nameservers 8.8.4.4 8.8.8.8

iface eth1 inet static
  address 192.168.1.1/24
  dns-nameservers 8.8.4.4 8.8.8.8

iface eth2.104 inet static
  address 192.168.2.1/24
  dns-nameservers 8.8.4.4 8.8.8.8

iface eth3.105 inet static
  address 192.168.3.1/24
  dns-nameservers 8.8.4.4 8.8.8.8

iface tun0 inet dhcp

We’ll define some routes in /etc/iproute2/rt_tables:

100 lan
101 vpnclient
103 guestsvlan
104 iotvlan
105 altcnx
255	local
254	main
253	default

add vlan support module in our load list in /etc/modules and reboot:

8021q

We also want devices to have proper IP addresses in the various ranges using dhcp.

apt install kea-dhcp4-server/stable

Note: ISC DHCP was recently phased out on Debian and can be replaced by KEA DHCP.
A tool to convert from the initial ISC to KEA is available here.

I attribute a fixed IP to all my LAN and IoT VLAN devices and leave guests on dynamic attribution.
I require doing so to be able to count on one IP to be attributed to the intended person/use.
You still can stick to just dynamic pools (and guests are on dynamic) but it’s less secure.

{
  "Dhcp4": {
    "authoritative": true,
    "client-classes": [
      {
        "name": "unifi"
      },
      {
        "name": "sub#unifi#0",
        "test": "concat(substring(pkt4.htype,-1,all), pkt4.mac) == 0x01802aa8d0fc47"
      },
      {
        "name": "sub#unifi#1",
        "test": "concat(substring(pkt4.htype,-1,all), pkt4.mac) == 0x01e063dab64a88"
      },
      {
        "name": "sub#unifi#2",
        "test": "concat(substring(pkt4.htype,-1,all), pkt4.mac) == 0x017483c233c860"
      },
      {
        "name": "sub#unifi#3",
        "test": "concat(substring(pkt4.htype,-1,all), pkt4.mac) == 0x017483c220c101"
      },
      {
        "name": "gen#_AND_#!unifi#",
        "test": "not member('unifi')"
      }
    ],
    "subnet4": [
      {
        "id": 1,
        "subnet": "192.168.0.0/24",
        "interface": "eth1",
        "pools": [
          {
            "pool": "192.168.0.224 - 192.168.0.253"
          }
        ],
        "option-data": [
          {
            "space": "dhcp4",
            "name": "routers",
            "code": 3,
            "data": "192.168.0.1"
          },
          {
            "space": "dhcp4",
            "name": "broadcast-address",
            "code": 28,
            "data": "192.168.0.255"
          },
          {
            "space": "dhcp4",
            "name": "domain-name-servers",
            "code": 6,
            "data": " 94.140.14.14, 94.140.14.14"
          }
        ],
        "valid-lifetime": 259200,
        "max-valid-lifetime": 604800
      },
      {
        "id": 2,
        "subnet": "172.16.0.0/24",
        "interface": "eth3.105",
        "pools": [
          {
            "pool": "172.16.0.224 - 172.16.0.255",
            "client-class": "gen#_AND_#!unifi#"
          }
        ],
        "option-data": [
          {
            "space": "dhcp4",
            "name": "routers",
            "code": 3,
            "data": "172.16.0.1"
          },
          {
            "space": "dhcp4",
            "name": "broadcast-address",
            "code": 28,
            "data": "172.16.0.255"
          },
          {
            "space": "dhcp4",
            "name": "domain-name-servers",
            "code": 6,
            "data": "8.8.8.8, 8.8.9.9"
          }
        ],
        "valid-lifetime": 259200,
        "max-valid-lifetime": 604800
      },
      {
        "id": 3,
        "subnet": "192.168.2.0/24",
        "interface": "eth2",
        "pools": [
          {
            "pool": "192.168.2.3 - 192.168.2.15"
          }
        ],
        "option-data": [
          {
            "space": "dhcp4",
            "name": "routers",
            "code": 3,
            "data": "192.168.2.1"
          },
          {
            "space": "dhcp4",
            "name": "broadcast-address",
            "code": 28,
            "data": "192.168.2.255"
          },
          {
            "space": "dhcp4",
            "name": "domain-name-servers",
            "code": 6,
            "data": "84.200.69.80, 84.200.70.40"
          }
        ],
        "valid-lifetime": 259200,
        "max-valid-lifetime": 604800
      }
    ],
    "interfaces-config": {
      "interfaces": [
        "eth1",
        "eth3.105",
        "eth2"
      ]
    },
    "host-reservation-identifiers": [
      "hw-address"
    ],
    "reservation-mode": "global",
    "reservations": [
      {
        "hostname": "Imac",
        "hw-address": "80:4a:14:65:ac:de",
        "ip-address": "192.168.0.4",
      },
      {
        "hostname": "PC",
        "hw-address": "58:af:34:42:21:11",
        "ip-address": "192.168.0.5",
      },
      ...
      {
        "hostname": "Guest1",
        "hw-address": "aa:d1:77:77:c4:48",
        "ip-address": "172.16.0.39"
      }
    ]
  }
}

On your ISP router, find where you can configure a DMZ IP address and input there 192.168.0.1, the IP of your firewall. If asked for ports, redirect them all toward 192.168.0.1. While you’re there, turn off WPS for the WIFI (often very vulnerable and can leak your WIFI WPA2 key). Once your Unifi APs are up and running, you’ll disable your router WIFI to avoid perturbations since embedded components are less effective.

Install openvpn (apt install openvpn), buy a nordvpn subscription (or whichever you want), configure it and enable it in /etc/defaults.

Next, we prepare the multirouting and setup a firewall.
For this example, I’ve installed a 4G modem/router on the DMZ ethernet. You can add it to your IoT VLAN or to a free ethernet port of the machine. You’ll have a 4G backup if the primary connexion is down.
What this script does, ultimately, is to create different routing tables and associate them with a mark.
This mark will later be used in our firewall to know when to send traffic XYZ through one or the other routing table. Here our FDDI/DSL router is sitting on 192.168.0.2, our 4G is on 192.168.0.3, and the firewall is on 192.168.0.1.

Ok in some /etc/init.d/multiroute.sh file, dump this:

#!/bin/bash

### BEGIN INIT INFO
# Provides:             multiroute
# Required-Start:       $network
# Required-Stop:        $network
# Should-Start:
# Should-Stop:
# Default-Start:        2 3 4 5
# Default-Stop:         0 1 6
# Short-Description:    Multiroute manager
# Description:          Manage multi-routing
### END INIT INFO

Set_variables()
{
  BLUE="\e[1;36m" && YELLOW="\e[1;33m" && ORANGE="\e[38;5;208m" && PURPLE="\e[1;35m"
  GREEN="\e[1;32m" && RED="\e[1;31m" && END="\e[0m" && OLDIFS="$IFS"
  DATE=`date +'%b %d %k:%M:%S'`
  WAN="eth0"
  LAN="eth1"
  GUESTVLAN="eth2.104"
  IOTVLAN="eth3.105"
  VPNCLIENT=`ifconfig|grep tun0`
  [[ ! -z "$VPNCLIENT" ]] && VPNCLIENTIF="tun0" && VPNCLIENTIP=`ip -o addr | grep -v inet6 | grep $VPNCLIENTIF | awk '{split($4, a, "/"); print a[1]}'` && VPNCLIENTROUTE=`ip route show|grep -v inet6 | grep "tun0 proto" | cut -f 1 -d " "`
}

Env_Cleanup()
{
  echo -e "$RED -> CLEANING UP FWMARKS$END"
  ip rule del from all fwmark 1    2>/dev/null
  ip rule del from all fwmark 2    2>/dev/null
  ip rule del from all fwmark 3    2>/dev/null

  echo -e "$RED -> CLEANING UP IP RULES$END"
  ip rule del lookup lan           2>/dev/null
  ip rule del lookup vpnclient     2>/dev/null
  ip rule del lookup guestvlan     2>/dev/null
  ip rule del lookup iotvlan       2>/dev/null
  ip rule del lookup altcnx        2>/dev/null

  echo -e "$RED -> FLUSHING ROUTING TABLES$END"
  ip route flush table lan         2>/dev/null
  ip route flush table vpnclient   2>/dev/null
  ip route flush table guestvlan   2>/dev/null
  ip rule del lookup iotvlan       2>/dev/null
  ip route flush table altcnx      2>/dev/null
}

Routing_Init()
{
  echo -e "$ORANGE -> CREATING MULTI-ROUTING TABLE $END"
  [[ $VPNCLIENT ]] && echo -e "$ORANGE -> VPN IS UP (route: $VPNCLIENTROUTE, on dev: $VPNCLIENTIF, ip: $VPNCLIENTIP) $END

  echo -e "$GREEN -> CREATING LAN ROUTING TABLE $END"
  ip route add table lan default                             dev $WAN         via 192.168.0.2
  ip route add table lan 192.168.1.0/24                      dev $LAN         src 192.168.1.1
  ip route add table lan 192.168.2.0/24                      dev $GUESTVLAN   src 192.168.2.1
  ip route add table lan 192.168.3.0/24                      dev $IOTVLAN     src 192.168.3.1
  [[ $VPNCLIENT ]] && ip route add table lan $VPNCLIENTROUTE dev $VPNCLIENTIF src $VPNCLIENTIP
  ip rule  add table lan from 192.168.1.2

  echo -e "$GREEN -> CREATING VPN CLIENT ROUTING TABLE $END"
  [[ $VPNCLIENT ]] && ip route add table vpnclient default         dev $VPNCLIENTIF via $VPNCLIENTIP
  [[ $VPNCLIENT ]] && ip route add table vpnclient $VPNCLIENTROUTE dev $VPNCLIENTIF src $VPNCLIENTIP
  [[ $VPNCLIENT ]] && ip rule  add table vpnclient from $VPNCLIENTIP

  echo -e "$GREEN -> CREATING 4G BACKUP CONNEXION ROUTING TABLE $END"
  ip route add table altcnx default dev $WAN via 192.168.0.3
  ip rule  add table altcnx from 192.168.0.3
}

Env_Init()
{
  echo -e "$BLUE -> SETTING UP KERNEL IPV4 PARAMETERS$END"
  for i in /proc/sys/net/ipv4/conf/*/rp_filter;  do echo 0 | tee "$i" >/dev/null; done 
  for i in /proc/sys/net/ipv4/conf/*/arp_filter; do echo 0 | tee "$i" >/dev/null; done 
  echo 1 | tee /proc/sys/net/ipv4/ip_forward > /dev/null                               

  echo -e "$BLUE -> TAGGING DEFAULT FWMARKS PER NETWORK $END"
  ip rule add from all fwmark 1 table lan
  ip rule add from all fwmark 2 table guestvlan
  [[ $VPNCLIENT ]] && ip rule add from all fwmark 2 table vpnclient
  ip rule add from all fwmark 2 table iotvlan
  ip rule add from all fwmark 3 table altcnx
  ip route flush cache

  echo -e "$BLUE -> ADDING TEST ROUTE (1.1.1.1) FOR FDDI TO 4G BACKUP $END"
  route add -host 1.1.1.1 gw 192.168.0.3 eth0 &> /dev/null          # FDDI test host & route when on 4G backup
}

case "$1" in

start)
  Set_variables
  [[ -z $VPNCLIENT ]] && sleep 5        # Wait for VPN to be up if not yet started when the firewall is started at boot time
  /usr/bin/logger -t "Multi route" "Starting" -p4
  /usr/bin/logger -t "Multi route" "VPN CLIENT DETECTED, ADDING RULES" -p4
  echo -e "$PURPLE --------------> $DATE: Multi routing Start <-------------$END"
  Env_Cleanup
  Routing_Init
  Env_Init
  echo -e "$PURPLE ----------------------> Multi routing active <---------------------$END"
  exit 0
;;

stop)
  Set_variables
  echo -e "$RED ------------------> Deactivating Multi Route ! <-------------------$END"
  /usr/bin/logger -t "Multi route" "Stopped" -p4
  echo 0 | sudo tee /proc/sys/net/ipv4/ip_forward > /dev/null
  ip rule del from all fwmark 1 2>/dev/null
  ip rule del from all fwmark 2 2>/dev/null
  ip rule del from all fwmark 3 2>/dev/null
  ip route flush cache
  echo -e "$RED ----------------------> Firewall disabled <------------------------$END"
  exit 0
;;

restart)
  /usr/bin/logger -t "Multiroute" "restart initiated" -p4
  $0 stop
  sleep 1
  $0 start
;;

*)
  echo -e "$YELLOW Usage: /etc/init.d/multiroute.sh {start|stop|restart}$END"
  exit 1
;;

esac
exit 0

Ok nothing fancy, to finish the job, simply run:

update-rc.d multiroute defaults

Now the firewall (adapt to your needs), I included a few examples:

#!/usr/sbin/nft -f

define wan            = eth0
define lan            = eth1
define guestvlan      = eth2.104
define iotvlan        = eth3.105
define vpn            = tun0           # created by OpenVPN client
define localhost      = lo
define public_eths    = { $wan, $vpn }  
define local_eths     = { $localhost, $lan, $guestvlan, $iotvlan } # local area eth
define workstation    = 192.168.1.4
define gamestation    = 192.168.1.5
define fwopenports    = { 22, 80, 443, 8080 }
define tcp_game_ports = { 1024-1124, 2456-2458, 3216, 9876, 9960-9969, 11100, 18000, 18060, 18120, 27900, 28910, 29900 }
define udp_game_ports = { 1024-1124, 3074, 3478, 4379-4380, 9876, 18000, 27000-27031, 27036, 29900, 37000-40000 }
define cameras        = { 192.168.1.8, 192.168.1.9, 192.168.1.10 }
define lanservices    = { 192.168.1.3, 192.168.1.5, 192.168.1.7 }
define multicasts     = { 239.255.255.250, 224.0.0.251 }
define bogons         = { 0.0.0.0/8,10.0.0.0/8,100.64.0.0/10,127.0.0.0/8,169.254.0.0/16,172.16.0.0/8,192.0.0.0/24,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,224.0.0.0/3 }
define icmp_v6        = { destination-unreachable, echo-reply, echo-request, nd-neighbor-solicit, nd-router-advert, nd-neighbor-advert, packet-too-big, parameter-problem, time-exceeded }

flush ruleset

table netdev retag {
    chain QoS {
          type filter hook ingress device $lan priority -149; policy accept;
          ip saddr $workstation                           ip dscp set cs6 # pro traffic, highest priority
          ip saddr $gamestation tcp dport $tcp_game_ports ip dscp set cs5 # gaming fairly high
          ip saddr $gamestation udp dport $udp_game_ports ip dscp set cs5 # gaming fairly high
          ip saddr $gamestation udp sport {50000, 50001}  ip dscp set cs1 # bittorrent, very low
          ip saddr $gamestation tcp sport {50000, 50001}  ip dscp set cs1 # bittorrent, very low
          ip dscp set cs3                                                 # all the rest default priority
    }
}

table ip mangle {
  chain prerouting {
    type filter hook prerouting priority -150; policy accept;
      mark != 0x0                               accept            # if no mark is set, pass
      iifname $guestvlan                        meta mark set 0x2 # send guest vlan traffic through VPN
      meta l4proto tcp     tcp    dport 50001   meta mark set 0x2 # send bittorrent traffic through VPN
      meta l4proto udp     udp    dport 50001   meta mark set 0x2 # send bittorrent traffic through VPN
      #ip saddr 192.168.1.22 tcp dport {80,443}   meta mark set 0x2 # send all web traffic from given IP through VPN
  }

  chain postrouting {
    type filter hook postrouting priority -150; policy accept;
      ct mark set mark
  }
}

table ip nat {
  chain prerouting {                                       # Public IP inbound traffic routing
    type nat hook prerouting priority -100; policy accept;
      iif $wan tcp dport 443   dnat to 192.168.1.49        # HA, not used if you install tailscale
      iif $wan tcp dport 5000  dnat to 192.168.1.75        # Frigate, not used if you install tailscale
      iif $wan tcp dport 50001 dnat to $gamestation        # torrent
      iif $wan udp dport 50001 dnat to $gamestation        # torrent
  }

  chain output {
    type nat hook output priority -100; policy accept;
  }

  chain postrouting {
    type nat hook postrouting priority 100; policy accept;
      oif     $wan       snat to 192.168.0.1
      oifname $vpn       masquerade         
  }
}

table inet filter {
  set prewhitelist {
    type ipv4_addr . inet_service
    flags timeout
  }

  chain input {
    type filter hook input priority 0; policy drop;
      ct state related,established                                        accept # already established connexions are preserved
      iif $local_eths                                                     accept # local IPs are allowed to reach the firewall 
      iif $public_eths ip saddr != @whitelist                             drop # log prefix "Not whitelisted:"
      iif $lan           tcp dport != $fwopenports                        log prefix "LAN SCAN: "        drop
      iifname $guestvlan tcp dport != $fwopenports                        log prefix "GUEST VLAN SCAN: " drop
      iif $public_eths ip frag-off & 0x1fff != 0                          log prefix "Frags: "           drop
      iif $public_eths ip saddr $bogons                                   log prefix "Bogons: "          drop
      iif $public_eths ct state invalid                                   log prefix "Invalid packet: "  drop
  }

  chain forward {
    type filter hook forward priority 0; policy drop;
      ct state established,related                                             accept # preserve established traffic
      iif $local_eths udp dport {53,123}                                       accept # dns & ntp
      iif $lan ip saddr $cameras tcp dport {465,587}                           accept # cam stmps
      iif $lan ip saddr $cameras                                               drop
      iif $lan                                                                 accept # forward any other lan born traffic
      iif $local_eths oif $public_eths                                         accept # accept to forward any local traffic to Wan
      iifname $guestvlan ip daddr $lanservices                                 accept # accept vlan ip to access certain lan services
      iifname $guestvlan oifname $wan                                          accept # accept guest vlan access to the internet
      iifname $iotvlan oifname $wan                                            accept # accept iot vlan access to the internet
      iifname $vpn ip daddr $gamestation meta l4proto {udp,tcp} th dport 50001 accept # bittorrent
      oifname $guestvlan ip daddr 192.168.1.0/24                               log prefix "FORWARD VLAN IP trying to reach LAN:" drop
      iif $public_eths ct state invalid                                        log prefix "Invalid packet:" drop
  }

  chain output {
    type filter hook output priority 0; policy accept;
  }
}

4. Connexion redundancy, Tailscale & CrowdSec

Tailscale

Tailscale is super simple to install. Either download the APP on your phones (iOS and Android have the app on their store), it’s available too for docker containers and on a debian:
curl -fsSL https://tailscale.com/install.sh | sh

The installer will then tell you to login on a specific URL, do it, and your phones and firewall, frigate, containers, etc will all be joining a private VPN allowing everyone to communicate nicely.
For HA, there is even an already existing addon, it’s just that simple. Blackmagic this thing really… And then in your HA app on your phone, just install tailscale, input your 100.xxx.yyy.zzz:8123 IP in your HA server config and you’ll seamlessly, securely, access your HA APP whether at home on when roaming around.

CrowdSec

curl -s https://packagecloud.io/install/repositories/crowdsec/crowdsec/script.deb.sh | sudo bash
apt install crowdsec crowdsec-firewall-bouncer-nftables crowdsec-custom-bouncer

What CrowdSec will do is to prevent bruteforce attacks on your ssh, port scans and also LAN initiated scans…
Remember these lines in our firewall?

      iif $lan           tcp dport != $fwopenports                        log prefix "LAN SCAN: "        drop
      iifname $guestvlan tcp dport != $fwopenports                        log prefix "GUEST VLAN SCAN: " drop

Well, this is all about Local machines, guests or IoT going rogue on you and trying to scan your firewall. Quite a classical lateral-move tactic, but they won’t expect your firewall to actually catch their attempts and tell you. So if one day Daikin, Sonos, an IoT object or a friend is compromised and start trying to hack you, they’ll fail and you’ll just know. For that, we need to configure a “custom bouncer”.
The classical bruteforce, port scans, http scans and all will be dealt with out of the box, but this one needs a bit of config.

From /etc/crowdsec/bouncers/crowdsec-custom-bouncer.yaml:

bin_path: /usr/local/scripts/pushover_crowdsec.sh
piddir: /var/run/
update_frequency: 10s
cache_retention_duration: 10s
daemonize: true
log_mode: file
log_dir: /var/log/
log_level: info
api_url: http://localhost:8080/
api_key: [your API key here]

Ok and guess what is in /usr/local/scripts/pushover_crowdsec.sh ?

#!/bin/bash

[[ `echo $2 | cut -f 1,2 -d"."` == "192.168" ]] && curl -s -F "token=[your pushover token here]" -F "user=[your pushover user token here]" -F "title='LAN Scan'" -F "message=Scanned by $2" https://api.pushover.net/1/messages

A simple pushover notification. Obviously, you can use any script or notification engin you’d fancy.

Also, you can install CrowdSec on your HA itself with the addon and it will take care of login/pass bruteforce on your HA instance.
(GitHub - crowdsecurity/home-assistant-addons: Home Assistant Crowdsec Addons)

Connexion redundancy

#!/bin/bash

Push()
{
  curl -s -F "token=[your pushover token here]" -F "user=[your pushover user token here]" -F "message=$1" https://api.pushover.net/1/messages
}

MQTT() {
  mosquitto_pub -h localhost -p 1883 -u [YOUR MQTT USER]] -P [YOUR MQTT PASS] -t $1 -m $2
}

Switch_4G() {
    touch /tmp/SWAP_4G
    route del default gw 192.168.1.2 eth0
    route add default gw 192.168.2.2 eth2
    nft insert rule ip mangle prerouting position 4 ip saddr 192.168.0.0/24 meta mark set 0x00000004 return
    Push "[FDDI: Outage confirmed, 4G backup active $TEST_FDDI]"
}

Switch_FDDI() {
    Handle=`nft -a list table ip mangle | grep "00000004" | awk '{print $NF}'`
    route del default gw 192.168.2.2 eth2
    route add default gw 192.168.1.2 eth0
    nft delete rule ip mangle prerouting handle $Handle
    [[ -f "/tmp/SWAP_4G" ]] && rm /tmp/SWAP_4G
    [[ -f "/tmp/FDDI_FLAP" ]] && rm /tmp/FDDI_FLAP
    Push "[FDDI: Outage ended, back to FDDI connexion $TEST_FDDI]"
}

Check_connexion()
{
  echo "[ Testing FDDI connexion ]"
  TEST_FDDI=`ping -c 3 -m 1 9.9.9.9 | grep rtt | cut -f 6 -d '/' | awk '{print int($1+0.5)}'`
  MQTT "homeassistant/connexion/ping" $TEST_FDDI

  if [ -z $TEST_FDDI ]; then
    [[ ! -f "/tmp/FDDI_FLAP" ]] && touch /tmp/FDDI_FLAP && Notify "[FDDI: Potential outage detected, switching to 4G if confirmed (ping: $TEST_FDDI)]"
    [[ -f "/tmp/FDDI_FLAP"   ]] && [[ ! -f "/tmp/SWAP_4G" ]] && Switch_4G
    MQTT "homeassistant/connexion/state" "KO"
  fi

  [[ "$TEST_FDDI" -gt "18" ]] && touch /tmp/FDDI_SLOW && Notify "[FDDI: connexion is lagging (ping: $TEST_FDDI)]"

  if [ "$TEST_FDDI" -le "19" ]; then
    [[ -f "/tmp/FDDI_FLAP" ]] && [[ ! -f "/tmp/SWAP_4G" ]] && rm /tmp/FDDI_FLAP && Notify "[FDDI connexion back to normal, 4G not activated (ping: $TEST_FDDI)]"
    [[ -f "/tmp/SWAP_4G"   ]] && Switch_FDDI
    [[ -f "/tmp/FDDI_SLOW" ]] && rm /tmp/FDDI_SLOW && Notify "[FDDI: connexion recovered from laggy state (ping: $TEST_FDDI)]"
    MQTT "homeassistant/connexion/state" "OK"
  fi
}

Check_connexion

5. Configuring your switch & Wifi to separate your networks

Let’s define VLANs in our switches.
Go to your admin interface and head to switching → VLAN → advanced. In VLAN configuration, add a 104 guestvlan and a 105 iotvlan.
Then in advanced → vlan membership, untag all port of vlan ID 1 (the image shows U) and the port where the cable connected to your firewall port for guest and iot vlan should be “blank”, have no letter on them. (you can click on them to change their letters)

Then change VLAN ID to 104 and tag the port where you have the cable coming from your firwall guest vlan to your switch with a “T”
Switch to VLAN ID 105 and do the same with the port where the cable coming from your firewall will receive IOT Vlan traffic, place the “T”.
CRITICAL: On both VLAN 104 and 105 place a “T” letter on the ports where your UNIFI wifi APs are connected.

In VLAN 104 & 105: all ports are blank but the one where your AP are connected and the firewall cable is linked to your switch.

Ok you can check in the menu below “Port PVID configuration”, you should see ports belonging to 1,104,105 for APs and just 104 or 105 for the cable connexion going to your firewall.
If you have some Sonos, head to multicast router configuration, enable on the port of your Unifi APs and the IoT port.

Alright, nearly there.

Now let’s install and configure the unifi manager.
Go to your firewall, and type the following:

sudo apt-get update && sudo apt-get install ca-certificates apt-transport-https
echo 'deb https://www.ui.com/downloads/unifi/debian stable ubiquiti' | sudo tee /etc/apt/sources.list.d/100-ubnt-unifi.list
sudo wget -O /etc/apt/trusted.gpg.d/unifi-repo.gpg https://dl.ui.com/unifi/unifi-repo.gpg
sudo service unifi start

Now head to the Unifi manager, and Adopt & configure your Unifi Wifi APs.

Once done, click the settings button on the left hand side and click it.
Then from there, create 3 networks. Home, Guests, IoT.
Needless to say, pick a decent WPA3 key to protect your Wifis.

Then create 3 networks, Home, Guests, IoT.
Give Home 192.168.1.0/24, Guests 192.168.2.0/24, IoT 192.168.3.0/24 and in Advanced, put the VLAN ID 104 for Guests and 105 for IoT. Back in Wifi configuration, bind each wifi network to its IP network (Home → home, Guests → guests, IoT → IoT)


For IoT I recommend to stick to WPA2, since most won’t support WPA3, broadcast only on 2.4 Ghz and avoid setting some minimum data rate control. You can leave multicast enhancement and the multicast and broadcast control to OFF. Later on, you can use this script to update your AP centrally (or Home assistant): wget https://get.glennr.nl/unifi/install/unifi-6.5.55.sh

6. Securing your HA

HA team pays a lot of attention to the security of HA itself and gives you tools for your password safety level, the risk of using an extension or another. They also cautiously maintain their code base and OS and are very well taken care of. Yet this is also an open-source tool, with external contributions and users of varying levels.

You can harden your HA by taking those simple steps:

  • Have strong passwords, on HA but also any Wifi, APP or API dependency
  • Install CrowdSec security engine + bouncer in HA also (to deter bruteforce attempts)
  • Install the least possible amount of 3rd party integrations and add-ons
  • Limit the number of users and their rights, give them good passwords
  • Not installing any add-ons with bad security requirements or ratings
  • Avoid dependency on 3rd party cloud-based tools
  • Stick with the updates, the more catchup you have, the more complex and most painful it becomes
  • Have an automated backup system, encrypted, exported to the cloud if the place takes fire or gets flooded (Google Cloud backup addon is gold)

Conclusion

  • LAN security: check
  • Guests & IoT isolation: check
  • Wifi security: check
  • Remote connexion security: check
  • HA security: check
  • Protection if an IoT goes rogue: check
  • Connexion redundancy: check
  • Privacy: check (DNS are filtering ads server and non tracking + VPN if need be and you can install adguard)
16 Likes

I see you wrote a lot and thank you for contributing but I have one question.
From security perspective is using ssl on local network a bit overkill or good security practice in your opinion?
I saw some guides on securing your network and ha but I can’t remember that anyone is speaking about securing you local services with ssl protocol.
I done this. I installed ha in container and created domain for it and subdomains for other containers. Using nginx and lets encrypt I got certs for domain and all subdomain I’m hosting.
My domain and subdomains are not accessible over net because I reroute dns traffic for domain back to my ha ip address.

It’s always good to cipher connexions. SSL everywhere you can :slight_smile:
In a (Home) LAN environment, though, if you’re worried about someone eavesdropping on the HTTP exchanges, you already have a more significant problem. You consider your local users as dangerous. In this case, I’d recommend creating an isolated WIFI and letting them live in a different environment.

1 Like

The thing is that you basically never know. I don’t have router that support vlans but from my perspective, and maybe I’m wrong as I’m not professional in this field, there is considerably more threats from the inside your network than from outside. Because you know, you have a family they all have wifi password, they come and go, they are all using different operating systems and you just never know.

I agree this is a risk, hence segmentation & segregation of your network in three zones is crucial.
1 trusted, 2 guests, 3 IoT, that way, if anything splashes, they are in a blast container.

Let me ask you another thing. I have a cisco rv320 router. It’s a dual wan router I bought during covid pandemic as then I had two lte routers. I used that because I worked from home and when ever my nephews turn on youtube my remote connection died. So I used two lte routers because I couldn’t argue with them any more.
Now, this router does support vlans but this is not wifi router.
I have xiaomi mesh wifi for my local network and the main mesh node is connected to my provider huawei router. Can I use this cisco router for a wifi vlan and if I can how to do it?

1 Like

Your threats consist of two factors.

  1. the potential damage being done
  2. the potential risk of it being exploited

Outside threats often have a potential damage that is somewhat low, because you would have made risk management, but there are many that seek to use any potential risk, so that is high.

Inside threats often have a potential damage that is really high, because you often need to open up for users and services that needs access to data on a whole other level that for outside users and services.
Users and services located inside your network is often though known and controlled by you, which limits the potential risk.

Risk management can come in 4 types.

  1. Mitigated (you have done something to lower the risk and the left over risk is then a new risk to be risk managed)
  2. Insuranced (often not a possibility for normal persons, al though some risks like ID theft might be insurancable (is that a word?) today)
  3. Accepted (You know the risk and potential damage, but have to accepted it. Some left over risk will always have to be accepted, because the price of mitigating or insuring is too high)
  4. Ignored (this should really never be accepted as an option, but it is often the case, because people think the risk or potential damage is low, but they have never really looked into it)

Some try to put numeric values on the risk potential and the potential damage to rate the threats. Some just try to put them into a low/medium/high category.

Thanks for the writeup.

Here is a perspective that might be useful when designing for computer security (from Marcus Ranum):

https://ranum.com/security/computer_security/editorials/dumb/index.html

I’m sorry I wouldn’t know because I never owned any of these hardware myself.

@kameo4242 Thanks for the writing.
Question: You go the route of building the firewall on linux instead of using something like Opnsense. Any particular reason for that? Do you see these firewall less reliable than the linux approach?
Thanks

Opnsense is based on Freebase, which is a Unix variant, like Linux.
Opnsense just gives a more polished and complete setup, but if you are confident with Linux, then it can be just as good.

Sorry I just saw the update. I lagged.

So, several points here. First thing first, OpnSense & PFSense are absolutely great products, brilliantly assembled and carefully curated. In a word, nothing against them.

I’m an old-timer. So I’ve been using IP chain, IP tables, nf tables, PF and all that jazz for a while already. So they aren’t frightening for me. And having direct control over them allows me to do things I couldn’t with OPN or PFSense.

So more flexibility, more advanced usage and it’s has stable as it can be, perfectly optimized for my use case, etc.

The point of sharing this here is to allow everyone interested to dive in advanced security.
I’m also integrating more & more tailscale to replace port knocking. Not exactly the same (like knocking would protect you against unknown zero-day overflow in ssh/tailscale, etc.) but should be good enough.

Wow such an amazing write up. I am using Synology as my backbone to my network.

I have the Synology RT6600 as my router and I use the WRX560 as mesh access points.

I’ve just finished reading your other article about starting off right in HA. Such a wealth of knowledge and I’m grateful for the time you spent. I am heading your advice and starting slow and targeted.

I have had some minor issues so far with the VLAN segregation and what I think may be due to the mesh routing. Do you have any suggestions or tips for me with my Synology backbone.

I can write up a more detailed breakdown of my overall network later.

Thanks for your writeup! I can adopt important things to my system. I have few questions for your UniFi setup:

  1. You mentioned that “avoid setting some minimum data rate control” in case of the IoT network. I agree with that, but the settings on the WiFi tab is not clear for me. If I would disable the “Auto” in the checkbox, there will be an exact value settings where I have to define how much Mbps should be the rate. Did you mean keep this settings on “Auto” or what is the value of the rate here?
  2. What do you think of the “Client Device Isolation” in the IoT network? also in the UniFi…

If I’ll have more time to do a writeup of my system, I would bind it to yours:) I have the same security needs as you. My hw tools are a bit different so it would be added value if I could post my config as well. I work with Mikrotik routers/managed switch, HP mini server with Proxmox, where HA, Adguard and UniFi Controller have separated environment (VM/LXC depends on). I also use VLAN network separation, OpenVPN for VPN and hate any cleartext traffic:)

1/ Yeah, I’m not trusting much auto-setting in unify context because they are often 0 or 1 but rarely dynamic. ie, the power output, set to auto == max.
Here, having a good control over the rates allow to avoid outdated 802.11b devices to clutter the network. Having a subnet with only IoT is also what I ended up having so if they want to go slow, they can, without crippling the rest of the network. Mine is set to 2 mbps as we speak on the IoT segment of the wifi network.

2/ It’s useful in an IoT environment usually. They are not supposed to talk to each other, except for the Chromecast, airplay Sonos and the likes. You’re anyway likely to have those on your primary network or use the function that allow you to fine tune who is able to broadcast (terrible for wifi perf) using the “Multicast and Broadcast control” in your setting → wifi configuration of the unifi controller.

I’m eagerly waiting for your writeup because this is typically unusual posts that offer great value for the few people interested in security :wink:

Even though the NSA got hacked, their Best Practices for Securing Your Home Network guide is useful.

Anyone have recommendations on investing in hardware for this strategy?

I love this guide and @kameo4242’s approach! I intend to follow the guide but am interested in first exploring the options that spending more money could introduce.

For example, buying all of the hardware from someone like Ubiquiti or Aruba etc to potentially reduce the number of components while also gaining a single management interface?

Typically you would actually increase the number of components, because you would need a server for running the management software, but other than that it should be no problem with trying to limiting the number of manufacturers.
Some might say that it will be putting all your eggs in one basket and it might even be a single point of failure should malware get into the manufactures software, but on the other hand equipment from same manufacture usually work easier together and the use of same terminology makes it easier to set up and thereby improve the security in general.
Just avoid using the cloud services some of those manufacturers provide, because that can be a loop hole into your network.

You should make sure that the APs, router and firewall can handle VLANs and not just pass-through.

Avoiding cloud management seems to be the sticking point. I’m looking at Aruba/HPE Instant On, Cisco Merkai Go and Ubiquiti and its tough to tell how much of their cloud stuff is actually required.

I use a EdgeRouter 4 from Ubiquiti and you can connect it to the cloud, but it seems to give no bad effects of not doing so.
The EdgeRouter is part of their EdgeMaxbseries, which is for ISPs according to their description, so it is not as such bound to their consumer products.
I use Cisco APs though sonit is a mix.
Cisco is not easy to work with because they are really limiting their support If you do not have an active service subscription.