commit 8c2f438749e346ecf641544c995dd308be25e001 Author: Maurice Date: Wed Aug 20 17:11:32 2025 +0200 Initial commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..9c34e0b --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +Hardening security + +https://hackviser.com/tactics/hardening/smb +https://hackviser.com/tactics/hardening/caddy +https://hackviser.com/tactics/hardening/ssh +https://hackviser.com/tactics/hardening/smtp +https://hackviser.com/tactics/hardening/rdp + +https://hackviser.com/tactics/pentesting + +https://hackviser.com/tactics/pentesting/services/ssh \ No newline at end of file diff --git a/SETUP-WORKSTATION.md b/SETUP-WORKSTATION.md new file mode 100644 index 0000000..d8a369b --- /dev/null +++ b/SETUP-WORKSTATION.md @@ -0,0 +1,5 @@ +# Chronyd blocks at startup + +chronyd takes care of keeping the system clock in sync. When the system boots, chronyd will block start-up until it has resolved the time. This is useful on systems without a hardware clock (to avoid the system booting as 1970-01-01), but annoying for this setup. + +This behaviour can be disabled by editing /etc/conf.d/chronyd and setting FAST_STARTUP=yes. \ No newline at end of file diff --git a/SETUP.md b/SETUP.md new file mode 100644 index 0000000..6b81ab8 --- /dev/null +++ b/SETUP.md @@ -0,0 +1,38 @@ +# Installation on hardware (Raspberry PI) +Raspberry Pi imager (.img) + +> If no internet connection: setup-interfaces with DHCP, rc-service networking restart + +```sh +setup-alpine +us +us + +hostname: alpi +ip: 192.168.2.22/24 +gateway: router (192.168.2.254) +netmask 255.255.255.0 + +Europe/Amsterdam +chrony +1 +YOURUSERNAME +YOURNAME +github.com/YOURUSERNAME.keys + +openssh +y +mmcblk0 +sys +y +reboot +``` + +FIRST. Make sure your public key is configured for SSH! Else, next step will LOCK YOU OUT SSH! + +Then, run install.sh by getting it from the internet (wget is in busybox): +```sh +wget https://TODO +chmod +x install.sh +./install.sh +``` \ No newline at end of file diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..b11640a --- /dev/null +++ b/install.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +# echo "Installing Git & cloning installation scripts" +apk add git +# git clone TODO + +# Install podman-openrc tool +apk add cargo +cargo install podman-openrc + +base_dir=$(pwd) + +# Run installation scripts +cd ./installation +source ./basic.sh +source ./podman.sh +source ./firewall.sh + +cd "$base_dir" + +# Run update script +source ./update.sh \ No newline at end of file diff --git a/installation/basic.sh b/installation/basic.sh new file mode 100644 index 0000000..77d3d55 --- /dev/null +++ b/installation/basic.sh @@ -0,0 +1,17 @@ +#!/bin/sh +echo "Basic setup" + +# Enable community repo +sed -i 's|^#\(http.*/community\)$|\1|' /etc/apk/repositories +apk update + +# Cron jobs +rc-update add crond +cat << EOF > /etc/periodic/daily/chrony +#!/bin/sh +chronyc makestep +EOF + +# Allow local.d services +rc-update add local default +rc-service local start \ No newline at end of file diff --git a/installation/firewall.sh b/installation/firewall.sh new file mode 100644 index 0000000..a1e6c9e --- /dev/null +++ b/installation/firewall.sh @@ -0,0 +1,14 @@ +#!/bin/sh +echo "Setting up firewall..." + +apk add -u awall # important -u flag! +apk add ip6tables iptables +modprobe -v ip_tables +modprobe -v ip6_tables +modprobe -v iptable_nat #if NAT is used + +# Register services +rc-update add iptables +rc-update add ip6tables +rc-service iptables start +rc-service ip6tables start \ No newline at end of file diff --git a/installation/podman.sh b/installation/podman.sh new file mode 100644 index 0000000..6a16ee0 --- /dev/null +++ b/installation/podman.sh @@ -0,0 +1,28 @@ +#!/bin/sh +echo "Installing Podman..." + +apk add podman iptables podman-compose +rc-update add cgroups +rc-service cgroups start + +# Rootless mode +adduser -D podman +modprobe tun +echo tun >> /etc/modules +echo podman:100000:65536 > /etc/subuid +echo podman:100000:65536 > /etc/subgid +doas su -c "podman system migrate" podman + +# Get rid of podman compose docker warning +touch /etc/containers/nodocker + +# Fix shared mount with local service +cat << EOF > /etc/local.d/mount-rshared.start +#!/bin/sh +mount --make-rshared / +EOF + +chmod +x /etc/local.d/mount-rshared.start + +# Allow ports >= 53 to be rootless bound +sysctl net.ipv4.ip_unprivileged_port_start=53 \ No newline at end of file diff --git a/services/basic/global.policy.json b/services/basic/global.policy.json new file mode 100644 index 0000000..2198fe0 --- /dev/null +++ b/services/basic/global.policy.json @@ -0,0 +1,11 @@ +{ + "description": "Restrict all internet access", + "variable": { "internet_if": "eth0" }, + "zone": { + "internet": { "iface": "$internet_if" } + }, + "policy": [ + { "in": "internet", "action": "drop" }, + { "action": "reject" } + ] +} \ No newline at end of file diff --git a/services/basic/icmp.policy.json b/services/basic/icmp.policy.json new file mode 100644 index 0000000..3d3370c --- /dev/null +++ b/services/basic/icmp.policy.json @@ -0,0 +1,11 @@ +{ + "description": "Allow ping-pong", + "filter": [ + { + "in": "internet", + "service": "ping", + "action": "accept", + "flow-limit": { "count": 10, "interval": 6 } + } + ] +} \ No newline at end of file diff --git a/services/basic/outgoing.policy.json b/services/basic/outgoing.policy.json new file mode 100644 index 0000000..82cd1e3 --- /dev/null +++ b/services/basic/outgoing.policy.json @@ -0,0 +1,11 @@ +{ + "description": "Allow outgoing connections for http/https, dns, ssh, ntp, ssh and ping", + "filter": [ + { + "in": "_fw", + "out": "internet", + "service": ["http", "https", "dns", "ssh", "ntp", "ping"], + "action": "accept" + } + ] +} \ No newline at end of file diff --git a/services/caddy/config/Caddyfile b/services/caddy/config/Caddyfile new file mode 100644 index 0000000..a220c7a --- /dev/null +++ b/services/caddy/config/Caddyfile @@ -0,0 +1,62 @@ +# https://hackviser.com/tactics/hardening/caddy +{ + auto_https disable_redirects + + # Do not write access logs to journald. + log { + exclude http.log.access + } + + # Write access logs to the logs volume in json + # format. Only keep logs for the last 30 days. + log access { + format json + output file /data/logs/access.log { + roll_keep_for 720h + } + } +} + +# Block with default http config that accepts requests on +# fd/3 and redirects to https. +(https-redir) { + bind fd/3 { + protocols h1 + } + redir https://{host}{uri} 308 +} + +# Block with default https config that accepts requests on +# fd/4 and fdgram/5. +(https) { + bind fd/4 { + protocols h1 h2 + } + bind fdgram/5 { + protocols h3 + } +} + +# Block with compression configuration. +(compression) { + encode zstd gzip +} + +# Block with headers that should be used by most +# sites. Add HSTS and some other security headers. +# Remove the server header because without it caddy +# leaks the backend server version. +# https://scotthelme.co.uk/a-new-security-header-referrer-policy/ +# https://scotthelme.co.uk/content-security-policy-an-introduction/ +(default-headers) { + header { + Strict-Transport-Security max-age=31536000; includeSubDomains; preload + X-Content-Type-Options nosniff + X-Frame-Options sameorigin + Content-Security-Policy default-src 'self'; img-src 'self' data:; script-src 'self'; style-src 'self' 'unsafe-inline'; + Referrer-Policy: same-origin + -Server + } +} + +import *.caddy diff --git a/services/caddy/http.policy.json b/services/caddy/http.policy.json new file mode 100644 index 0000000..24ffb7f --- /dev/null +++ b/services/caddy/http.policy.json @@ -0,0 +1,11 @@ +{ + "description": "Allow incoming http (TCP 80 & 443) ports", + "filter": [ + { + "in": "internet", + "out": "_fw", + "service": ["http", "https"], + "action": "accept" + } + ] +} diff --git a/services/caddy/service.toml b/services/caddy/service.toml new file mode 100644 index 0000000..bdd52c1 --- /dev/null +++ b/services/caddy/service.toml @@ -0,0 +1,33 @@ +user = "podman" +capabilities = ["NET_BIND_SERVICE"] + +[service] +name = "caddy" +image = "caddy:alpine" + +[[mounts]] +typ = "bind" +source = "$HOME/caddy" +target = "/etc/caddy" +read_only = true + +[[volumes]] +source = "caddy-logs" +target = "/data/logs" +create = true + +[[volumes]] +source = "caddy-data" +target = "/data/caddy" +create = true + +[[ports]] +host = 80 +container = 80 + +[[ports]] +host = 443 +container = 443 + +[[networks]] +group = "caddy" \ No newline at end of file diff --git a/services/caddy/update.sh b/services/caddy/update.sh new file mode 100644 index 0000000..26992fc --- /dev/null +++ b/services/caddy/update.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +# Symlink config files in base dir +find "$base_dir" -name "*.caddy" -exec ln -sf {} "./config" \; + +# Symlink config dir +mkdir -p /home/podman/caddy +ln -sf ./config /home/podman/caddy \ No newline at end of file diff --git a/services/ssh/ssh.caddy b/services/ssh/ssh.caddy new file mode 100644 index 0000000..30d74d2 --- /dev/null +++ b/services/ssh/ssh.caddy @@ -0,0 +1 @@ +test \ No newline at end of file diff --git a/services/ssh/ssh.policy.json b/services/ssh/ssh.policy.json new file mode 100644 index 0000000..d5c2238 --- /dev/null +++ b/services/ssh/ssh.policy.json @@ -0,0 +1,12 @@ +{ + "description": "Allow limited incoming SSH access (TCP/22)", + "filter": [ + { + "in": "internet", + "out": "_fw", + "service": "ssh", + "action": "accept", + "conn-limit": { "count": 3, "interval": 30 } + } + ] +} \ No newline at end of file diff --git a/services/ssh/sshd_config b/services/ssh/sshd_config new file mode 100644 index 0000000..b464098 --- /dev/null +++ b/services/ssh/sshd_config @@ -0,0 +1,25 @@ +# SSHD config. See https://man.openbsd.org/sshd_config +# https://hackviser.com/tactics/hardening/ssh + +# Protocol 2 is more secure +Protocol 2 + +# No root login or passwords +PermitRootLogin no +PasswordAuthentication no +AuthenticationMethods publickey + +# Allow tunneling, but not with option R (remote) +AllowTcpForwarding local +GatewayPorts yes + +# override default of no subsystems +Subsystem sftp internal-sftp + +# Only allow users that are listed +AllowUsers admin + +# Only allow secure ciphers +KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,mlkem768x25519-sha256 +Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com +MACs hmac-sha2-256,hmac-sha2-512 \ No newline at end of file diff --git a/services/ssh/update.sh b/services/ssh/update.sh new file mode 100644 index 0000000..bd67ed2 --- /dev/null +++ b/services/ssh/update.sh @@ -0,0 +1,2 @@ +#!/bin/sh +ln -sf ./sshd_config /etc/ssh/sshd_config \ No newline at end of file diff --git a/setup-alpine.answerfile b/setup-alpine.answerfile new file mode 100644 index 0000000..2b7c5eb --- /dev/null +++ b/setup-alpine.answerfile @@ -0,0 +1,38 @@ +# See: https://wiki.alpinelinux.org/wiki/Using_an_answerfile_with_setup-alpine + +# Keymap +KEYMAPOPTS="us us" + +# Host name +HOSTNAMEOPTS="-n alpi" +DNSOPTS=none + +# Contents of /etc/network/interfaces +INTERFACESOPTS="auto lo +iface lo inet loopback + +auto eth0 +iface eth0 inet static + address 192.168.2.22 + netmask 255.255.255.0 + gateway 192.168.2.254 +" + +TIMEZONEOPTS="-z Europe/Amsterdam" + +PROXYOPTS=none + +# User +USEROPTS="-a -u -g audio,input,video,netdev admin" +USERSSHKEY="https://github.com/maurictg.keys" + +# First repo +APKREPOSOPTS="-1" + +SSHDOPTS="-c openssh" +NTPOPTS="-c chrony" + +# Data disk +DISKOPTS="-m sys /dev/mmcblk0" +LBUOPTS=none +APKCACHEOPTS=none \ No newline at end of file diff --git a/todo.txt b/todo.txt new file mode 100644 index 0000000..2026455 --- /dev/null +++ b/todo.txt @@ -0,0 +1,3 @@ +backup(), restore() + +Volume labels (label) \ No newline at end of file diff --git a/update.sh b/update.sh new file mode 100644 index 0000000..0f3d84d --- /dev/null +++ b/update.sh @@ -0,0 +1,29 @@ +#!/bin/sh +base_dir=$(pwd) + +echo "Updating service scripts..." +podman-openrc ./services /etc/init.d/ + +# Update services (awall policies, scripts) +for service in "./services"/*/; do + [ -d "$service" ] || continue + cd "$service" || continue + + # Run update.sh if present + if [ -f "update.sh" ]; then + source ./update.sh + fi + + # Symlink and activate each *.policy.json + for policy in *.policy.json; do + [ -e "$policy" ] || continue + POLICY_NAME="${policy%.policy.json}" + ln -sf "./$policy" "/etc/awall/optional/$POLICY_NAME.policy.json" + awall enable "$POLICY_NAME.policy" + done + + cd "$base_dir" +done + +echo "Activating firewall..." +awall activate \ No newline at end of file