Linux
DIY Linux Router - Part 3 - Users, Security and Firewall
This is the third part of a multi-part series describing how to build your own Linux router.
- Part 1: Initial Setup
- Part 2: Network and Internet
- Part 4: Podman and Unbound
- Part 5: Wifi
- Part 6: Nextcloud and Jellyfin
- Part 7: File Sharing
- Part 8: Backup
- Impermanence Storage
In the first and second parts, we installed the operating system, configured the network, and set up the Mac Mini to work as a router.
In this part, we will increase security by creating users, changing SSH authentication, and hardening the firewall configuration.
EAA AirVenture Oshkosh 2013 Wall of Fire
Table of Contents
Users
Create intended users. You can create whatever user you need. In my case, I will have three: one to act as an administrator user named admin
, another for rootless containers as podman
, and another named git
to have a personal and private Git repository.
For the podman
user, if you're using the impermanence Storage, you need to allocate the subuid
and subgid
ranges for the user.
1. Generate Hashed Password (optional)
This step is optional, as the intended way to authenticate on the server is through SSH using SSH Keys
, but you can create a password if you want to ask for one when using sudo
or authenticating locally.
Create the password for the users:
mkpasswd --method=SHA-512
Password: #type the password (hackme00)
$6$ZeLsWXraGkrm9IDL$Y0eTIK4lQm8D0Kj7tSVzdTJ/VbPKea4ZPf0YaJR68Uz4MWCbG1EJp2YBOfWHNSZprZpjpbUvCIozbkr8yPNM0.
2. SSH Keys
Generate the private and public key pair or use an existing one. More about it at this Github's article.
Execute the following SSH key generation steps on your computer.
ssh-keygen -t ed25519 -C "[email protected]" -f ~/.ssh/router-admin
Generating public/private ed25519 key pair.
Enter passphrase (empty for no passphrase):
Enter the same passphrase again:
Your identification has been saved in /root/.ssh/router-admin
Your public key has been saved in /root/.ssh/router-admin.pub
The key fingerprint is...
Retrieve your SSH public keys and copy the content:
cat ~/.ssh/router-admin.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC... [email protected]
Keep your private key safe and do not share it with anyone.
Repeat the same process for every user you want to create.
3. Create `users.nix` in `/etc/nixos/modules/`
Access the server via SSH using the user root
and the password
defined during the installation in part 1 of this tutorial and do as follows:
Set the intended users by replacing the values in openssh.authorizedKeys.keys
. In the example, the key for the admin
user should be filled with the value from ~/.ssh/router-admin.pub
.
/etc/nixos/modules/users.nix
{ config, pkgs, ... }: {
users.users.root.initialHashedPassword = "##HashedPa$$word"; # You can remove this line if you do not want to log directly with root user.
users.users = {
# Admin user
admin = {
uid = 1000;
isNormalUser = true;
description = "Administrator User";
home = "/home/admin"; # Home directory
extraGroups = [ "wheel" ]; # Add the user to the 'wheel' group for sudo access
initialHashedPassword = "$6$rounds=656000$example$hashedpassword"; # Password, optional
openssh.authorizedKeys.keys = [
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC..." # Replace with the actual public key
];
};
# Podman User for rootless pods
podman = {
uid = 1001;
subUidRanges = [{ startUid = 100000; count = 65536; }];
subGidRanges = [{ startGid = 100000; count = 65536; }];
isNormalUser = true;
description = "Podman Rootless";
home = "/home/podman";
group = "containers";
openssh.authorizedKeys.keys = [
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC..." # Replace with the actual public key
];
linger = true; # Lingering enables systemd user services to start up without logging into user account.
};
# Git user
git = {
uid = 1002;
isNormalUser = true;
description = "Git";
home = "/home/git";
openssh.authorizedKeys.keys = [
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC..." # Replace with the actual public key
];
};
};
users.groups.containers = {
gid = 993;
members = [ "podman" ];
}
# Enable sudo for users in the 'wheel' group
security.sudo = {
enable = true;
wheelNeedsPassword = false; # Optional: Requires a password to be entered for the sudo command. Set to true if you choose to create the admin user with a password.
};
}
4. Disable password authentication over SSH
Disable password authentication and root
login through SSH.
/etc/nixos/modules/services.nix
services = {
...
openssh = {
enable = true;
settings.PermitRootLogin = "no"; # Was "yes". Change to "no" to disable
settings.PasswordAuthentication = false;
};
...
};
5. Update the configuration and try to log in
Rebuild the config:
nixos-rebuild switch
Try to log in to the server using the admin
using the private key generated earlier.
ssh -i ~/.ssh/router-admin [email protected]
6. Create the SSH configuration file
If you do not want to type ssh -i ~/.ssh/router-admin [email protected]
everytime to authenticate into the serverm, on your computer, configure the file ~/.ssh/config
as following:
Host router-admin
Hostname 10.1.78.1
user admin
IdentityFile ~/.ssh/router-admin
Host router-podman
Hostname 10.1.78.1
user podman
IdentityFile ~/.ssh/router-podman
Host router-git
Hostname 10.1.78.1
user git
IdentityFile ~/.ssh/router-git
Try sshing to the server without providing the identity file as a parameter.
ssh router-admin
7. Lock the root Account (optional)
Since the SSH service is configured to prevent root
logins, I don't see the need to lock the root
account, but you can do so if you want.
If you configured your server with impermanence storage, remove the line users.users.root.initialHashedPassword = "##HashedPa$$word"
from the users.nix
to lock out the root password without further steps.
CAUTION Be aware that locking the root
password without a password for the admin
account will prevent you from logging in locally, but only via SSH. Also, make sure you have created the admin
account and added it to the wheel
group.
passwd -l root
Firewall
With our user configurations complete, it's time to enhance our firewall security.
Current Setup
So far, our firewall configuration includes:
- Allowing all incoming traffic from the LAN network.
- Blocking all incoming traffic from WAN/PPPoE, Guest, and IoT networks except for internet access.
While this setup provides a basic level of security, we can achieve better protection through more granular traffic control. By doing so, we ensure that if any service unintentionally opens additional ports on the server, unauthorized traffic will not be allowed through.
Planned Enhancements
We will refine our firewall to allow only the traffic necessary for each network:
- LAN network: Allow DHCP and SSH services.
- Guest and IoT networks: Allow only DHCP service.
- WAN: Enable SSH to remote access.
Organizing the Firewall Configuration
To simplify management and ensure scalability, we will structure our firewall configuration into logical sections. This approach divides interfaces, rules, and services into zones and organizes the configuration across multiple files. The structure will look as follows:
**INET Table** (Main firewall rules)
sets.nft
: Map interfaces to their respective zones.services.nft
: Define chains for service ports, such as SSH and HTTP.zones.nft
: Specify which services are allowed in each zone.rules.nft
: Configure rules for zones and manage traffic flow.
**NAT Table** (Network Address Translation rules)
nat_sets.nft
: Map interfaces to their respective zones.nat_chains.nft
: Define NAT chains for tasks like port redirection.nat_zones.nft
: Associate NAT chains with zones.nat_rules.nft
: Configure NAT rules for zones.
This modular approach will make the firewall configuration more organized, easier to understand, and simpler to maintain or extend in the future.
Set up files
1. Remove the nftables.nft file
As we will split the nftables into discrete files, there's no need to use this file anymore. You can delete or just let inactive.
rm /etc/nixos/modules/nftables.nft
2. Create the the NFTables Configuration Files
Create the directory and all NFTables files needed.
mkdir -p /etc/nixos/nftables
touch /etc/nixos/nftables/{nat_chains,nat_rules,nat_sets,nat_zones,rules,services,sets,zones}.nft
Configure every intended NFTables file.
sets.nft
Let's make use of variables to address the interfaces name dinamically.
cat << EOF > /etc/nixos/nftables/sets.nft
table inet filter {
set WAN {
type ifname;
elements = { \$if_wan }
}
set LAN {
type ifname;
elements = { \$if_lan }
}
set GUEST {
type ifname;
elements = { \$if_guest }
}
set IOT {
type ifname;
elements = { \$if_iot }
}
}
EOF
services.nft
cat << EOF > /etc/nixos/nftables/services.nft
table inet filter {
chain dhcp_input {
udp dport 67 ct state { new, established } counter accept comment "Allow DHCP"
}
chain echo_input {
icmp type echo-request accept
icmp type echo-reply accept
}
chain public_ssh_input {
tcp dport ssh ct state { new, established } limit rate 10/minute burst 50 packets counter accept comment "Allow SSH traffic with rate limiting"
}
chain ssh_input {
tcp dport ssh ct state { new, established } counter accept comment "Allow SSH"
}
}
EOF
zones.nft
cat << EOF > /etc/nixos/nftables/zones.nft
table inet filter {
chain LAN_INPUT {
jump dhcp_input
jump echo_input
jump ssh_input
}
chain GUEST_INPUT {
jump dhcp_input
jump echo_input
}
chain IOT_INPUT {
jump dhcp_input
jump echo_input
}
chain WAN_INPUT {
jump public_ssh_input
}
}
EOF
rules.nft
cat << EOF > /etc/nixos/nftables/rules.nft
table inet filter {
chain input {
type filter hook input priority filter; policy drop;
iifname "lo" counter accept
iifname @LAN jump LAN_INPUT
iifname @GUEST jump GUEST_INPUT
iifname @IOT jump IOT_INPUT
iifname @WAN jump WAN_INPUT
# Allow returning traffic from ppp0 and drop everything else
iifname @LAN ct state { established, related } counter accept
iifname @WAN ct state { established, related } counter accept
}
chain output {
type filter hook output priority 100; policy accept;
}
chain forward {
type filter hook forward priority filter; policy drop;
iifname @LAN oifname @WAN counter accept comment "Allow trusted LAN to WAN"
iifname @WAN oifname @LAN ct state established,related counter accept comment "Allow established back to LANs"
iifname @GUEST oifname @WAN counter accept comment "Allow trusted GUEST to WAN"
iifname @WAN oifname @GUEST ct state established,related counter accept comment "Allow established back to GUEST"
iifname @IOT oifname @WAN counter accept comment "Allow trusted IOT to WAN"
iifname @WAN oifname @IOT ct state established,related counter accept comment "Allow established back to IOT"
#Drop traffic between networks
iifname @GUEST oifname @LAN drop comment "Drop connections from GUEST to LAN"
iifname @IOT oifname @LAN drop comment "Drop connections from IOT to LAN"
iifname @GUEST oifname @IOT drop comment "Drop connections from GUEST to IOT"
iifname @IOT oifname @GUEST drop comment "Drop connection from IOT to GUEST"
#MSS Clamp
oifname @WAN tcp flags syn tcp option maxseg size set 1452
}
}
EOF
nat_sets.nft
cat << EOF > /etc/nixos/nftables/nat_sets.nft
table nat {
set WAN {
type ifname;
elements = { \$if_wan }
}
set LAN {
type ifname;
elements = { \$if_lan }
}
set GUEST {
type ifname;
elements = { \$if_guest }
}
set IOT {
type ifname;
elements = { \$if_iot }
}
}
EOF
nat_chains.nft
NAT Chains will be created as empty for now.
cat << EOF > /etc/nixos/nftables/nat_chains.nft
table ip nat {
}
EOF
nat_zones.nft
As far as there's no redirect chains, NAT zones will be created with empty chains for now.
cat << EOF > /etc/nixos/nftables/nat_zones.nft
table ip nat {
chain LAN_PREROUTING {
}
chain GUEST_PREROUTING {
}
chain IOT_PREROUTING {
}
chain WAN_PREROUTING {
}
}
EOF
nat_rules.nft
cat << EOF > /etc/nixos/nftables/nat_rules.nft
table ip nat {
chain prerouting {
type nat hook prerouting priority filter; policy accept;
iifname @LAN jump LAN_PREROUTING
iifname @GUEST jump GUEST_PREROUTING
iifname @IOT jump IOT_PREROUTING
iifname @WAN jump WAN_PREROUTING
}
chain postrouting {
type nat hook postrouting priority filter; policy accept;
oifname @WAN tcp flags syn tcp option maxseg size set 1452
oifname @WAN masquerade
}
}
EOF
3. Update the networking.nix Configuration File
Edit the network section of networking.nix configuration file as follows:
Update just the networking section. Let the remaining of the file as is.
/etc/nixos/modules/networking.nix
...
networking = {
useDHCP = false;
firewall.enable = false;
nftables = {
enable = true;
rulesetFile = pkgs.writeText "ruleset.conf" ''
define if_wan = "ppp0"
define if_lan = "${lan}"
define if_guest = "${guest}"
define if_iot = "${iot}"
define ip_lan = "${ip_lan}"
define ip_guest = "${ip_guest}"
define ip_iot = "${ip_iot}"
# Inet filter, services and rules
include "${../nftables/sets.nft}"
include "${../nftables/services.nft}"
include "${../nftables/zones.nft}"
include "${../nftables/rules.nft}"
# Nat & redirect
include "${../nftables/nat_sets.nft}"
include "${../nftables/nat_chains.nft}"
include "${../nftables/nat_zones.nft}"
include "${../nftables/nat_rules.nft}"
'';
flattenRulesetFile = true;
};
};
...
4. Rebuild the configuration and test
nixos-rebuild switch
Conclusion
With this configuration, we have improved the security of this firewall. What has been done so far:
- Reduced the need to use the
root
account for certain tasks. - Divided our firewall into zones, making it easier to manage.
- Created more granular control over the traffic on our server.
. In the next part, it is time to install Podman and configure our DNS Server with Unbound on it. - Part 4: Podman and Unbound
keywords: macmini • router • linux • nixos • pppoe • unifi • ubiquiti • apple • vlan • tl-sg108e