Compare commits

...

5 commits

Author SHA1 Message Date
Geir Okkenhaug Jerstad
de9c028072 Add reverse-proxy configuration with DMZ-specific security
Some checks are pending
🏠 Home Lab CI/CD Pipeline / 🔍 Validate Configuration (push) Waiting to run
🏠 Home Lab CI/CD Pipeline / 🔨 Build Configurations (push) Blocked by required conditions
🏠 Home Lab CI/CD Pipeline / 🔒 Security Audit (push) Blocked by required conditions
🏠 Home Lab CI/CD Pipeline / 📚 Documentation & Modules (push) Blocked by required conditions
🏠 Home Lab CI/CD Pipeline / 🔄 Update Dependencies (push) Waiting to run
🏠 Home Lab CI/CD Pipeline / 🚀 Deploy Configuration (push) Blocked by required conditions
🏠 Home Lab CI/CD Pipeline / 📢 Notify Results (push) Blocked by required conditions
- Create reverse-proxy machine configuration for VPS edge server
- Configure SSH access only via Tailscale (100.96.189.104)
- Implement strict DMZ firewall rules (HTTP/HTTPS only externally)
- Add enhanced fail2ban settings for DMZ environment
- Include sma user with SSH key management
- Configure Nginx reverse proxy with Let's Encrypt SSL
- Add reverse-proxy to flake.nix nixosConfigurations

Security features:
- SSH only accessible through Tailscale interface
- Aggressive fail2ban settings (24h ban, 3 max retries)
- Firewall rejects all non-essential traffic
- No common network config to avoid security conflicts
2025-06-05 16:48:45 +02:00
Geir Okkenhaug Jerstad
304e868e09 Add reverse-proxy configuration with DMZ-specific security
- Create reverse-proxy machine configuration for VPS edge server
- Configure SSH access only via Tailscale (100.96.189.104)
- Implement strict DMZ firewall rules (HTTP/HTTPS only externally)
- Add enhanced fail2ban settings for DMZ environment
- Include sma user with SSH key management
- Configure Nginx reverse proxy with Let's Encrypt SSL
- Add reverse-proxy to flake.nix nixosConfigurations

Security features:
- SSH only accessible through Tailscale interface
- Aggressive fail2ban settings (24h ban, 3 max retries)
- Firewall rejects all non-essential traffic
- No common network config to avoid security conflicts
2025-06-05 16:47:52 +02:00
Geir Okkenhaug Jerstad
2530b918ca fix: resolve configuration conflicts
- Fixed networking defaultGateway interface specification for sleeper-service
- Resolved shell alias conflict between geir and sma users
- Changed sma aliases from 'lab' to 'homelab' to avoid conflict
2025-06-05 16:33:29 +02:00
Geir Okkenhaug Jerstad
6fe8cdb790 feat: add NFS server and Transmission service to sleeper-service
- Created modules/services/nfs.nix for network file sharing
- Updated sleeper-service configuration with NFS and Transmission
- Fixed SSH key management to use direct key configuration
- Updated hardware-configuration to use sleeper-service hostname
- Added firewall ports for Transmission RPC (9091)
2025-06-05 16:31:09 +02:00
Geir Okkenhaug Jerstad
77e6b9a501 feat: Implement two-key SSH management strategy
- Add modules/security/ssh-keys.nix for centralized SSH key management
- Generate role-specific SSH keys with geir@geokkjer.eu email:
  - Admin key (geir@geokkjer.eu-admin) for sma user server access
  - Development key (geir@geokkjer.eu-dev) for geir user and git services
- Update SSH client config with role-based host patterns
- Configure users/geir.nix and users/sma.nix with appropriate key access
- Add SSH key setup to both machine configurations
- Create scripts/setup-ssh-keys.sh for key generation automation
- Update plan.md with completed SSH security implementation

Security benefits:
- Principle of least privilege (separate admin vs dev access)
- Limited blast radius if keys are compromised
- Clear usage patterns: ssh admin-sleeper vs ssh geir@sleeper-service.home
- Maintains compatibility with existing services during transition
2025-06-05 16:25:33 +02:00
14 changed files with 402 additions and 30 deletions

View file

@ -48,6 +48,18 @@
./modules/common/tty.nix
];
};
# reverse-proxy - VPS edge server with Nginx reverse proxy
reverse-proxy = nixpkgs.lib.nixosSystem {
inherit system specialArgs;
modules = [
./machines/reverse-proxy/configuration.nix
./machines/reverse-proxy/gandicloud.nix
./modules/common/nix.nix
./modules/common/base.nix
./modules/common/tty.nix
];
};
};
# Custom packages for the home lab
@ -70,6 +82,7 @@
echo "Available configurations:"
echo " - congenital-optimist (Threadripper workstation)"
echo " - sleeper-service (Xeon file server)"
echo " - reverse-proxy (VPS edge server)"
echo ""
echo "Build with: nixos-rebuild build --flake .#<config>"
echo "Switch with: nixos-rebuild switch --flake .#<config>"

View file

@ -9,6 +9,9 @@
./hardware-configuration.nix
../../modules/network/network-congenital-optimist.nix
# Security modules
../../modules/security/ssh-keys.nix
# System modules
../../modules/system/fonts.nix
../../modules/system/applications.nix

View file

@ -47,7 +47,7 @@
fsType = "zfs";
};
fileSystems."/mnt/storage/media" =
{ device = "files:/mnt/storage";
{ device = "sleeper-service:/mnt/storage";
fsType = "nfs";
options = [ "x-systemd.automount" "noauto" "x-systemd.idle-timeout=600" ];
};

View file

@ -0,0 +1,92 @@
{ pkgs, configs, lib, ... }:
let
Host = "vps1.tail807ea.ts.net";
in
{
imports = [
../../modules/common/base.nix
../../modules/users/sma.nix
../../modules/security/ssh-keys.nix
];
environment.systemPackages = with pkgs; [
neovim curl htop bottom fastfetch
tailscale git
];
# Hostname configuration
networking.hostName = "reverse-proxy";
# DMZ-specific firewall configuration - very restrictive
networking.firewall = {
enable = true;
# Only allow HTTP/HTTPS from external network
allowedTCPPorts = [ 80 443 ];
allowedUDPPorts = [ ];
# SSH only allowed on Tailscale interface (DMZ security)
interfaces.tailscale0.allowedTCPPorts = [ 22 ];
# Explicitly block all other traffic
rejectPackets = true;
};
# Security services
services.fail2ban = {
enable = true;
# Extra aggressive settings for DMZ
bantime = "24h";
maxretry = 3;
};
# Tailscale for secure management access
services.tailscale.enable = true;
# SSH configuration - ONLY accessible via Tailscale (DMZ security)
services.openssh = {
enable = true;
settings = {
PermitRootLogin = lib.mkForce "no";
PasswordAuthentication = false;
PubkeyAuthentication = true;
AuthenticationMethods = "publickey";
MaxAuthTries = 3;
ClientAliveInterval = 300;
ClientAliveCountMax = 2;
};
listenAddresses = [
{
addr = "100.96.189.104"; # Tailscale IP only
port = 22;
}
];
};
# nginx reverse proxy
services.nginx = {
enable = true;
recommendedGzipSettings = true;
recommendedOptimisation = true;
recommendedProxySettings = true;
recommendedTlsSettings = true;
virtualHosts = {
"git.geokkjer.eu" = {
addSSL = true;
enableACME = true;
locations."/".proxyPass = "http://apps:3000";
};
#"geokkjer.eu" = {
# default = true;
# forceSSL = true;
# enableACME = true;
# locations."/".proxyPass = "/var/wwww/homepage/";
#};
};
};
# acme let's encrypt
security.acme = {
acceptTerms = true;
defaults = {
email = "geir@geokkjer.eu";
};
};
}

View file

@ -0,0 +1,44 @@
# This is the configuration required to run NixOS on GandiCloud.
{ lib, modulesPath, ... }:
{
imports = [
(modulesPath + "/virtualisation/openstack-config.nix")
];
config = {
boot.initrd.kernelModules = [
"xen-blkfront" "xen-tpmfront" "xen-kbdfront" "xen-fbfront"
"xen-netfront" "xen-pcifront" "xen-scsifront"
];
# Show debug kernel message on boot then reduce loglevel once booted
boot.consoleLogLevel = 7;
boot.kernel.sysctl."kernel.printk" = "4 4 1 7";
# For "openstack console log show"
boot.kernelParams = [ "console=ttyS0" ];
systemd.services."serial-getty@ttyS0" = {
enable = true;
wantedBy = [ "multi-user.target" ];
serviceConfig.Restart = "always";
};
# The device exposed by Xen
boot.loader.grub.device = lib.mkForce "/dev/xvda";
# This is to get a prompt via the "openstack console url show" command
systemd.services."getty@tty1" = {
enable = lib.mkForce true;
wantedBy = [ "multi-user.target" ];
serviceConfig.Restart = "always";
};
# This is required to get an IPv6 address on our infrastructure
networking.tempAddresses = "disabled";
nix.extraOptions = ''
experimental-features = nix-command flakes
'';
system.stateVersion = "23.05";
};
}

View file

@ -2,6 +2,17 @@
imports = [
./hardware-configuration.nix
../../modules/network/network-sleeper-service.nix
# Security modules
../../modules/security/ssh-keys.nix
# Services
../../modules/services/nfs.nix
../../modules/system/transmission.nix
# User modules
../../modules/users/geir.nix
../../modules/users/sma.nix
];
# Boot configuration
@ -40,20 +51,10 @@
tree
];
# Users
users.users.geir = {
isNormalUser = true;
extraGroups = [ "wheel" "networkmanager" ];
shell = pkgs.zsh;
openssh.authorizedKeys.keys = [
# Add SSH public keys here
];
};
programs.zsh.enable = true;
# Firewall configuration
networking.firewall.allowedTCPPorts = [ 22 ];
networking.firewall.allowedTCPPorts = [ 22 9091 ]; # SSH and Transmission RPC
system.stateVersion = "25.05";
}

View file

@ -30,7 +30,10 @@
};
# Network gateway and DNS (based on nmap discovery)
defaultGateway = "10.0.0.138"; # Discovered router at lan.home
defaultGateway = {
address = "10.0.0.138"; # Discovered router at lan.home
interface = "enp0s25"; # Main ethernet interface
};
nameservers = [ "10.0.0.14" "10.0.0.138" "8.8.8.8" ]; # Pi-hole, router, Google DNS fallback
# Additional firewall ports for file server services

View file

@ -0,0 +1,81 @@
# SSH Key Management Module
# Two-key strategy: admin (sma) and development (geir)
{ config, pkgs, lib, ... }:
{
# Global SSH daemon configuration
services.openssh = {
enable = true;
settings = {
PasswordAuthentication = false;
KbdInteractiveAuthentication = false;
PermitRootLogin = "no";
PubkeyAuthentication = true;
};
# Use modern, secure algorithms only
extraConfig = ''
PubkeyAcceptedKeyTypes ssh-ed25519,ssh-ed25519-cert-v01@openssh.com
KexAlgorithms curve25519-sha256@libssh.org
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com
MACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com
'';
};
# SSH client configuration
programs.ssh = {
startAgent = true;
extraConfig = ''
# Default to development key for daily use
Host *
IdentityFile ~/.ssh/id_ed25519_dev
AddKeysToAgent yes
ServerAliveInterval 60
ServerAliveCountMax 3
# Admin access to servers (use sma user)
Host admin-* *.admin
User sma
IdentityFile ~/.ssh/id_ed25519_admin
# Git services (use geir user with dev key)
Host git.* github.com gitlab.com
User git
IdentityFile ~/.ssh/id_ed25519_dev
# Home lab servers (geir user for development access)
Host sleeper-service sleeper-service.home 10.0.0.8
User geir
IdentityFile ~/.ssh/id_ed25519_dev
Host grey-area grey-area.home 10.0.0.11
User geir
IdentityFile ~/.ssh/id_ed25519_dev
Host reverse-proxy reverse-proxy.home 10.0.0.12
User geir
IdentityFile ~/.ssh/id_ed25519_dev
# Admin access to servers (when needed)
Host admin-sleeper sleeper-service.admin
Hostname 10.0.0.8
User sma
IdentityFile ~/.ssh/id_ed25519_admin
Host admin-grey grey-area.admin
Hostname 10.0.0.11
User sma
IdentityFile ~/.ssh/id_ed25519_admin
Host admin-reverse reverse-proxy.admin
Hostname 10.0.0.12
User sma
IdentityFile ~/.ssh/id_ed25519_admin
# Tailscale network
Host 100.* *.tail*
User geir
IdentityFile ~/.ssh/id_ed25519_dev
'';
};
}

31
modules/services/nfs.nix Normal file
View file

@ -0,0 +1,31 @@
# NFS Server Configuration
# Network File System server for home lab storage
{ config, pkgs, ... }:
{
# NFS server configuration
services.nfs.server = {
enable = true;
# Export the storage directory
exports = ''
/mnt/storage 10.0.0.0/24(rw,sync,no_subtree_check,no_root_squash)
'';
# Create exports on startup
createMountPoints = true;
};
# Ensure the storage directory exists
systemd.tmpfiles.rules = [
"d /mnt/storage 0755 geir users -"
"d /mnt/storage/media 0755 geir users -"
"d /mnt/storage/downloads 0755 geir users -"
"d /mnt/storage/backups 0755 geir users -"
];
# Required packages for NFS
environment.systemPackages = with pkgs; [
nfs-utils
];
# Firewall rules are already configured in network module
}

View file

@ -5,16 +5,13 @@
enable = true;
user = "geir";
group = "users";
#home = "/mnt/storage/";
settings.rpc-port = 9091;
settings.rpc-bind-address = "0.0.0.0";
#openRPCPort = true;
downloadDirPermissions = "770";
settings = {
download-dir = "/mnt/storage";
#rpc-whitelist-enabled = true;
download-dir = "/mnt/storage/downloads";
rpc-whitelist = "127.0.0.1,10.0.0.*,100.*.*.*";
rpc-host-whitelist = "congenital-optimist,localhost";
rpc-host-whitelist = "sleeper-service,localhost";
};
};
}

View file

@ -21,6 +21,14 @@
shell = pkgs.zsh;
# SSH access with development keys
openssh.authorizedKeys.keys = [
# Current key (keep for continuity during transition)
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHeOvTHIw+hZOAiWkIrz9t11UeGwxAMx7jN/1IIdgq7O geokkjer@gmail.com"
# New development key
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHukJK0Kc1YexvzF8PdqaqWNZdVffGoM6ePPMecrU6dM geir@geokkjer.eu-dev"
];
# User-specific packages
packages = with pkgs; [
# Browsers & Communication

View file

@ -23,8 +23,8 @@
# SSH key-based authentication only (no password login)
openssh.authorizedKeys.keys = [
# Add SSH public key here when ready
# "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5... sma@home-lab"
# Admin key for server administration
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPgzKS1N7+7+N1/8U8++1pl4hapDm6TOy0QhrfrYA8mz geir@geokkjer.eu-admin"
];
# Essential admin packages
@ -103,9 +103,9 @@
"connections" = "ss -tuln";
# Git for infrastructure
"lab" = "cd /home/geir/Home-lab";
"lab-status" = "cd /home/geir/Home-lab && git status";
"lab-pull" = "cd /home/geir/Home-lab && git pull";
"homelab" = "cd /home/geir/Home-lab";
"homelab-status" = "cd /home/geir/Home-lab && git status";
"homelab-pull" = "cd /home/geir/Home-lab && git pull";
# Security
"audit-users" = "cat /etc/passwd | grep -E '/bin/(bash|zsh|fish)'";

24
plan.md
View file

@ -266,18 +266,19 @@ Home-lab/
- **10.0.0.8**: sleeper-service (Intel Xeon file server - rename from files.home)
- **10.0.0.11**: grey-area (planned application server)
- **10.0.0.12**: reverse-proxy (planned edge server)
- **10.0.0.14**: pi.hole (Pi-hole DNS/ad-blocker)
- **10.0.0.90**: wordpresserver.home (existing WordPress server)
- **10.0.0.117**: webdev.home (existing web development server)
- **10.0.0.138**: lan.home (router/gateway)
- **10.0.0.14**: pi.hole (Pi-hole DNS/ad-blocker) maybe move to nixos
- **10.0.0.90**: wordpresserver.home (existing WordPress server) to be deleted, incus container
- **10.0.0.117**: webdev.home (existing web development server) to be deleted, incus container
- **10.0.0.138**: lan.home (router/gateway/dhcp)
- **Tailscale Network (100.x.x.x/10)**:
- **100.109.28.53**: congenital-optimist (current machine)
- **100.119.86.92**: apps (active server) (rename to grey area)
- **100.114.185.71**: arlaptop (laptop) (Arch Linux with plans to migrate to NixOS)
- **100.81.15.84**: files (file server)
- **100.81.15.84**: files (file server rename to sleeper-service )
- **100.103.143.108**: pihole (DNS server)
- **100.96.189.104**: vps1 (external VPS) (rename to reverse proxy)
- **100.126.202.40**: wordpresserver (WordPress)
- **100.126.202.40**: wordpresserver (WordPress) to be deleted
- remind user to update tailsce or find a way to use the cli to do this
- [ ] **VLAN planning**: Consider network segmentation for different service types
- [ ] **DNS configuration**: Plan local DNS resolution for internal services
@ -377,9 +378,18 @@ Home-lab/
### 5.3 Security & Networking
- [x] **systemd-networkd migration**: Completed for sleeper-service with static IP configuration
- [x] **SSH key management centralization**: Implemented two-key strategy
- **Admin key** (`geir@geokkjer.eu-admin`): For sma user, server administration access
- **Development key** (`geir@geokkjer.eu-dev`): For geir user, git services, daily development
- **NixOS module**: `modules/security/ssh-keys.nix` centralizes key management
- **SSH client config**: Updated with role-based host patterns and key selection
- **Security benefits**: Principle of least privilege, limited blast radius if compromised
- **Usage examples**:
- `ssh geir@sleeper-service.home` - Uses dev key automatically
- `ssh admin-sleeper` - Uses admin key for sma user access
- `git clone git@github.com:user/repo` - Uses dev key for git operations
- [ ] VPN configuration (Tailscale expansion)
- [ ] Firewall rules standardization across machines
- [ ] SSH key management centralization
- [ ] Certificate management (Let's Encrypt)
- [ ] Network segmentation planning (VLANs for services vs. user devices)
- [ ] DNS infrastructure (local DNS server for service discovery)

89
scripts/setup-ssh-keys.sh Executable file
View file

@ -0,0 +1,89 @@
#!/usr/bin/env bash
# SSH Key Generation Script - Two-Key Strategy
# Generates admin and development SSH keys
set -euo pipefail
SSH_DIR="$HOME/.ssh"
HOSTNAME="$(hostname)"
EMAIL_BASE="geir@geokkjer.eu"
echo "🔑 Setting up SSH keys for $HOSTNAME"
echo "📧 Using email base: $EMAIL_BASE"
# Create SSH directory if it doesn't exist
mkdir -p "$SSH_DIR"
chmod 700 "$SSH_DIR"
# Backup existing key if it exists
if [[ -f "$SSH_DIR/id_ed25519" ]]; then
echo "💾 Backing up existing key to id_ed25519.backup"
cp "$SSH_DIR/id_ed25519" "$SSH_DIR/id_ed25519.backup"
cp "$SSH_DIR/id_ed25519.pub" "$SSH_DIR/id_ed25519.pub.backup"
fi
echo ""
echo "🔐 Generating two SSH keys:"
echo " 1. Admin key (for sma user, server administration)"
echo " 2. Development key (for geir user, git, daily use)"
echo ""
# Generate admin key
ADMIN_KEY="$SSH_DIR/id_ed25519_admin"
if [[ ! -f "$ADMIN_KEY" ]]; then
echo "🔐 Generating admin key..."
ssh-keygen -t ed25519 -f "$ADMIN_KEY" -C "$EMAIL_BASE-admin" -N ""
chmod 600 "$ADMIN_KEY"
chmod 644 "$ADMIN_KEY.pub"
echo "✅ Generated: $ADMIN_KEY"
else
echo "⏭️ Admin key already exists"
fi
# Generate development key
DEV_KEY="$SSH_DIR/id_ed25519_dev"
if [[ ! -f "$DEV_KEY" ]]; then
echo "🔐 Generating development key..."
ssh-keygen -t ed25519 -f "$DEV_KEY" -C "$EMAIL_BASE-dev" -N ""
chmod 600 "$DEV_KEY"
chmod 644 "$DEV_KEY.pub"
echo "✅ Generated: $DEV_KEY"
else
echo "⏭️ Development key already exists"
fi
echo ""
echo "🎯 Next steps:"
echo "1. Add these public keys to your NixOS configuration:"
echo "2. Deploy updated configuration to target servers"
echo "3. Test SSH access with both keys"
echo "4. Update external Git services with new development key"
echo ""
echo "📋 Public keys to add to NixOS configuration:"
echo ""
if [[ -f "$ADMIN_KEY.pub" ]]; then
echo "# Admin key (add to security.ssh-keys.admin in modules/security/ssh-keys.nix)"
echo "\"$(cat "$ADMIN_KEY.pub")\""
echo ""
fi
if [[ -f "$DEV_KEY.pub" ]]; then
echo "# Development key (add to security.ssh-keys.development in modules/security/ssh-keys.nix)"
echo "\"$(cat "$DEV_KEY.pub")\""
echo ""
fi
echo "💡 Usage examples:"
echo " ssh geir@sleeper-service.home # Uses dev key automatically"
echo " ssh admin-sleeper # Uses admin key for sma user"
echo " git clone git@github.com:user/repo # Uses dev key for git"
echo ""
echo "🔄 To update your Git remotes with the new key:"
echo " # Add new key to GitHub/GitLab first, then:"
echo " ssh -T git@github.com # Test the connection"
echo ""
echo "✅ SSH key setup complete!"