Das war mal wieder komplizierter als gedacht. So richtet man sein Netzwerk so ein, dass man einen Raspberry Pi hinter einem Unitymedia-Router via Dyndns erreichen kann.
Setup:
* Internet von Unitymedia via der Connectbox
* Eine Dyndns-Subdomain (bei mir von inwx)
* Ein via WLAN verbundener Raspberry Pi mit Debian Stretch (9.3)
Probleme, die es zu lösen gilt:
* Die Verbindung muss über IPv6 ablaufen, da Unitymedia keine allein genutzten IPv4 Adressen vergibt
* Der Raspberry Pi soll ein autoreconnect zum WLAN machen
* Die Connectbox muss die Pakete weiterleiten
* Der Raspberry Pi darf keine IPv6 Privacy Extensions verwenden (zufällige IP suffixe), weil sonst nach einer Woche die Filterregeln der Connectbox ihre Gültigkeit verlieren.
* Let’s Encrypt-Zertifikate für eine abhörsichere Verbindung
* Zugriffsbeschränkung
* Mein Smartphone sollte die Verbindung aufbauen können
Ich gehe mal davon aus, dass der Raspberry-Pi soweit funktioniert. Alle $ Befehle
beziehen sich auf die Linux-Shell des Raspberry Pi. Das $ Zeichen darf nicht mit eingegeben werden. Wie man Konfigurationsdateien bearbeitet, erklärt Paul McWhorter hier.
Mein Raspberry läuft mit Debian Stretch (9.3). Die Debian-Version lässt sich über
cat /etc/debian_version
herausfinden.
Wlan
Der offizielle Guide zeigt, wie man sich mit einem WLAN verbindet.
Um bei WLAN-Ausfall automatisch wiederverbunden zu werden, erstellt man eine neue Konfigurationsdatei ($ sudo nano /etc/network/interfaces.d/99-wlan
) mit folgendem Inhalt:
allow-hotplug wlan0
iface wlan0 inet manual
wpa-roam /etc/wpa_supplicant/wpa_supplicant.conf
iface default inet dhcp
IPv6 Adresse
Im Gegensatz zu den bekannten und quasi aufgebrauchten IPv4-Adressen (z.B. 221.14.142.35) gibt es von den IPv6-Adressen einfach unglaublich viele (z.B. 2a02:8071:1ec:d900:ba27:ebff:fef3:2eaf). So viele, dass Netzwerken ganze Teilbereiche zugewiesen werden, aus denen sich dann die Teilnehmer (z.B. der Raspberry) eine raussuchen können. Am Ende setzt sich so eine Adresse folgendermaßen zusammen:
2a02:8071:1ec:d900:ba27:ebff:fef3:2eaf
< prefix > < interface >
identifier
Das Präfix bekommt die Connectbox von Unitymedia zugewiesen und der Interface-Identifier enthält die MAC-Adresse des Teilnehmers (plus ff:fe
in der Mitte und einen Bitkipper im achten Bit). Da das nicht sehr Datenschutzfreundlich ist, gibt es die Privacy-Extensions, die den Interface-Indentifier zufällig machen und alle x Stunden wechseln. Und weil Unitymedia selbst ein bisschen Datenschutz machen will, wechselt auch das Präfix alle paar Tage. Das genaue Ablaufdatum sieht man auf der Connectbox (192.168.0.1) unter Admin -> Information -> IPv6 lease expire.
Der Raspberry (bzw Raspian) ist jetzt standardmäßig so eingestellt, dass er zwar nicht die Privacy-Extensions verwendet, aber sich seinen Interface-Identifier zumindest halb-zufällig erstellt, nämlich aus einer Kombination des Präfixes, der Mac-Adresse und der Uhrzeit. Das bedeutet, dass sich der Interface-Identifier jedes mal ändert, wenn sich das Präfix ändert. Da wir gleich Portfilter in der Connectbox einrichten wollen, muss zumindest der Interface-Identifier konstant bleiben. Die Connectbox ist schlau genug, den Präfix der IPv6-Adressen im Portfilter automatisch zu aktualisieren.
Dazu editieren wir $ sudo nano /etc/dhcpcd.conf
, suchen die Zeile
# Generate Stable Private IPv6 Addresses instead of hardware based ones
slaac private
und kommentieren die slaac private
Einstellung aus:
# Generate Stable Private IPv6 Addresses instead of hardware based ones
#slaac private
Nach einem Neustart sollte die IPv6-Adresse ($ ip -6 addr show dev wlan0
) die MAC-Adresse des WLAN-Adapters ($ ip link show dev wlan0
) enthalten (mit der oben beschriebenen Verwurstelung). Ersterer Befehl zeigt zwei Adressen an. Diejenige, die mit fe80:
startet ist die lokale Adresse, wie früher 192.168.x.x
.
Portfreigabe
In der Oberfläche der Connectbox (192.168.0.1) unter Erweiterte Einstellungen -> Sicherheit -> Ip und Port Filter -> IPv6 Port Filter erstellen wir eine neue eingehende Port-Regel. Wer schon früher Portregeln für IPv4 erstellt hat, muss aufpassen. Die Quellportangabe hat hier eine andere Bedeutung.
[x] Aktiviert [ ] Deaktiviert
Traffic policy: [x] Ja [ ] Nein
Protokoll: TCP
Quell IP-Adresse: Alle
Ziel IP-Adresse: <Die aktuelle Adresse des Raspberry Pi.
Diejenige, die NICHT mit fe80 anfängt.>
Quell Port Range: Beliebig !!!
Ziel Port Range: Manuell. Start: 80. Ende: 80.
Dann ein Klick auf „Regel übernehmen“ und ganz wichtig danach auf „Änderungen übernehmen“.
Dann noch eine zweite Portfreigabe für SSL-Verbindungen einrichten (SSL ist nicht SSH):
Ziel Port Range: Manuell. Start: 443. Ende: 443.
Dyndns
Da wird es jetzt komplizierter, da es verschiedene Anbieter gibt. Ich habe bei mir darauf geachtet, dass es für meine (Sub-)Domain nur einen AAAA-Record gibt und keinen A-Record. (Der A-Record übersetzt einen Domainnamen in eine IPv4-Adresse, der AAAA-Record in eine IPv6-Adresse). Ich will damit erreichen, dass Verbindungen von außen gar nicht erst versuchen, eine IPv4-Verbindung aufzubauen. Abgesehen davon, wohin sollte ich die leiten? 0.0.0.0?
Mein Anbieter ist inwx.de, da habe ich für 5 Euro im Jahr eine Domain. Dazu gibt es eine „kostenlose“ Dyndns-Funktion, bei der man dann den AAAA-Record per URL-Aufruf aktualisieren kann. Ich hab die DynDns-Funktion einfach für eine neue Subdomain eingerichtet. Es wurde automatisch ein A und ein AAAA-Record erstellt. Den A-Record hab ich dann einfach wieder gelöscht. Solange ich bei dem URL-Aufruf den Parameter myip=ipaddr
weglasse, wird der auch nicht mehr neu erstellt, so wie ich das wollte. Bei der Dyndns-Konfiguration erscheint interessanterweise die Fehlermeldung DynDNS-Record gelöscht
, aber es funktioniert trotzdem noch, den AAAA-Record zu aktualisieren.
Bei allen folgenden Befehlen/Scripten/Konfigurationsdateien musst du deinen eigenen Domainnamen eintragen. Meiner ist radi.invisibeltower.de
. Geheimhalten bringt nichts, denn alle Domains, für die ein Zertifikat ausgestellt wird, werden in einer großen Liste veröffentlicht.
Auf dem Raspberry-Pi erstellen wir ein Script, das die Dyndns-Url aktualisiert:
$ mkdir ~/domain
$ touch ~/domain/dyndns_update.sh
$ chmod +x ~/domain/dyndns_update.sh
$ nano ~/domain/dyndns_update.sh
mit dem folgenden Inhalt:
#!/bin/bash
HOSTNAME=radi.invisibletower.de
DYNDNSUSER=hier_username_eintragen
DYNDNSPW=hier_passwort_eintragen
DYNDNSURL=hier_die_update_url_bis_zum_Fragezeichen_aber_ohne_das_Fragezeichen_eintragen
CURRENT_IP6=$(ip address show dev wlan0 | grep ff:fe | grep -v fe80: | awk '{print $2}' | sed 's/^\([0-9a-f:]*\).*/\1/g')
DNS_IP6=$(host -t AAAA ${HOSTNAME} | tr " " "\n" | grep ff:fe)
if [[ "${CURRENT_IP6}" == "" ]]; then
date
echo ERROR - COULD NOT GET OWN IP
exit
fi
if [[ "${CURRENT_IP6}" == "${DNS_IP6}" ]]; then
# Printing if everything is ok just spams the log.
# echo OK.
:
else
date
echo The DNS knows ip: ${DNS_IP6}
echo The real ipv6 address is: ${CURRENT_IP6}
echo Renew DNS ...
curl ${DYNDNSURL}\?myipv6\=${CURRENT_IP6}\&hostname\=${HOSTNAME}\&username\=${DYNDNSUSER}\&password\=${DYNDNSPW}
echo
echo Done. Sleep 60 seconds.
sleep 60
echo Lookup again ...
DNS_IP6=$(host -t AAAA ${HOSTNAME} | tr " " "\n" | grep ff:fe)
if [[ "${CURRENT_IP6}" == "${DNS_IP6}" ]]; then
echo DNS ok
else
echo ERROR - COULD NOT UPDATE DNS \(or dns TTL is longer than expected\)
fi
fi
Ausführen via $ ~/domain/dyndns_update.sh
. Jetzt sollte der DNS-Teil schonmal funktionieren.
Wenn nicht, hilft vielleicht unboundtest.com bei der Fehlersuche. Mein Fehler war ein simpler Buchstabendreher im Domainnamen. Wenn was schief geht, hilft es oft auch, einfach 3600 Sekunden zu warten. Vor allem wenn es mit unbound funktioniert, mit host -t AAAA domain.de
aber nicht.
Damit das automatisch funktioniert, legen wir für dieses script einen cronjob an. Cronjobs führen Scripte regelmäßig ohne unser Zutun aus.
Mit $ sudo crontab -e
öffnen wir die Tabelle aller Cronjobs und fügen ans Ende folgende Zeile ein:
*/5 * * * * /home/pi/domain/dyndns_update.sh >> /var/log/dyndns_update.log
Let’s Encrypt Nr 1
Zu Beginn brauchen wir erstmal ein SSL-Zertifikat, das wir dann im nächsten Schritt benutzen können. Dazu hab ich mich an dieser Anleitung orientiert.
Let’s encrypt stellt ein sich selbst aktualisierendes Tool mit dem Namen certbot-auto bereit. Das installieren wir erstmal:
$ cd ~/domain
$ wget https://dl.eff.org/certbot-auto
$ chmod a+x certbot-auto
$ ./certbot-auto --version
Zum Aktualisieren der Zertifikate erstellen wir ein Script:
$ cd ~/domain
$ touch letsencrypt_init.sh
$ chmod +x letsencrypt_init.sh
$ nano letsencrypt_init.sh
mit folgendem Inhalt:
#!/bin/bash
DOMAIN=radi.invisibletower.de
service haproxy stop
/home/pi/domain/certbot-auto certonly --standalone -d $DOMAIN
mkdir -p /etc/haproxy/certs
cat /etc/letsencrypt/live/$DOMAIN/fullchain.pem /etc/letsencrypt/live/$DOMAIN/privkey.pem > /etc/haproxy/certs/$DOMAIN.pem
chmod -R go-rwx /etc/haproxy/certs
service haproxy start
Da kommt auch schon der haproxy drin vor. Die beiden service
-Befehle werden erstmal fehlschlagen, aber das ist ok. Ausführen mit sudo ~/domain/letsencrypt_init.sh
.
Die Zertifikate sollten jetzt zu finden sein: $ sudo ls -l /etc/haproxy/certs
Haproxy
Ich benutze weder apache noch nginx als Webserver. Stattdessen läuft ein Proxy auf Port 80 und 443 der einzelne Unterverzeichnisse an andere Serverprogramme auf dem Raspberry Pi (z.B. Octoprint) weiterleitet. Der Proxy heißt Haproxy. Er kümmert sich um sowohl um SSL als auch um die Authentifizierung.
Erstmal installieren mit $ sudo apt-get install haproxy
. Dann die Konfigurationsdatei $ sudo nano /etc/haproxy/haproxy.cfg
anpassen:
global
daemon
log 127.0.0.1 local0 debug
tune.ssl.default-dh-param 2048
defaults
log global
mode http
option httplog
option dontlognull
retries 3
option redispatch
option http-server-close
option forwardfor
maxconn 2048
timeout connect 5s
timeout client 15min
timeout server 15min
frontend http-in
bind :::80 v4v6
acl is_letsencrypt path_beg /.well-known/acme-challenge/
use_backend letsencrypt-backend if is_letsencrypt
redirect scheme https code 301 if !{ ssl_fc } !is_letsencrypt
frontend https-in
bind :::443 v4v6 ssl crt /etc/haproxy/certs/radi.invisibletower.de.pem
reqadd X-Forwarded-Proto:\ https
option forwardfor
acl myvaliduser http_auth(L1)
use_backend webcam if myvaliduser { path_beg /webcam/ }
use_backend octoprint if myvaliduser { path_beg /octoprint/ }
use_backend octoprint_socket if myvaliduser { path_beg /sockjs/ }
use_backend askforauth if !myvaliduser
backend webcam
reqrep ^([^\ :]*)\ /webcam/(.*) \1\ /\2
server webcam1 127.0.0.1:8080
backend octoprint
reqrep ^([^\ :]*)\ /octoprint/(.*) \1\ /\2
reqadd X-Script-Name:\ /octoprint
option forwardfor
reqadd X-Scheme:\ https if { ssl_fc }
server octoprint1 127.0.0.1:5000
backend askforauth
http-request auth realm Hallo
backend octoprint_socket
reqrep ^([^\ :]*)\ /(.*) \1\ /\2
server octoprint1 127.0.0.1:5000
backend letsencrypt-backend
server letsencrypt 127.0.0.1:54321
userlist L1
group G1
user meinusername insecure-password meinpasswort groups G1
user zweiter_username insecure-password anderespasswort groups G1
Das sieht kompliziert aus (Doku), aber ein bisschen muss man sich da reinfuchsen. Deswegen hier ein bisschen Kontext.
Ruft man eine Internetseite im Browser auf, passiert das über das sogenannte HTTP-Protokoll. Das legt fest, wie genau der Browser nach einer bestimmten URL fragt und wie der Server darauf antworten muss. Die Frage nennt man HTTP-Request, die Antwort HTTP-Response. Beide bestehen aus Klartext, erst kommen einige sogenannte Header, darauf folgt der eigentliche Inhalt, der bei Request meist leer ist und bei Responses den HTML-Code enthält. Der HTTP-Request, mit dem ich diesen Beitrag aufrufe, sieht beispielsweise so aus:
GET /?p=241&preview=true HTTP/1.1
Host: invisibletower.de
User-Agent: Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:58.0) Gecko/20100101 Firefox/58.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: https://invisibletower.de/
Cookie: wordpress_logged_in=geheim; wp-settings-1=geheim; wp-settings-time-1=geheim
DNT: 1
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0
Die erste Zeile darin gibt den Typ (GET/POST/..) und den Pfad an. Jede weitere Zeile darin ist ein Header und erfüllt irgendeine Funktion. Ein Webserver macht nichts anderes, als einen beliebigen Port zu öffnen (standardmäßig Port 80) und auf HTTP-Requests zu warten.
Der haproxy öffnet ebenendiesen Port 80, nimmt alle Anfragen an, schaut sich die Header an und leitet die Anfragen quasi als Mittelsmann nach den eigenstellten Regeln an andere Server weiter. Die Regeln können beispielsweise den aufgerufenen Pfad abfragen oder ob in den Headern ein korrektes Passwort mitgeschickt wurde. Außerdem kann haproxy den Request manipulieren, z.B. neue Header einfügen oder den Pfad verändern. Die „anderen“ Server können auf anderen Computern oder auf dem gleichen Computer, dann aber auf anderen Ports laufen. Ein Server ist quasi nur ein Programm, dass einen Port geöffnet hat. (PS: haproxy kann auch ganz normale Netzwerkverbindungen routen, die nicht das HTTP-Protokoll verwenden.)
Haproxy ist quasi ein Router. Um das intern umzusetzen, hat der haproxy sogenannte Frontends und Backends. Ein Frontend sagt, wo der haproxy Anfragen annimmt und an welches Backend sie weitergeleitet werden und das Backend sagt, wie der dahinterstehende Server erreichbar ist.
Gehen wir mal die Konfigurationsdatei durch.
frontend http-in
bind :::80 v4v6
acl is_letsencrypt path_beg /.well-known/acme-challenge/
use_backend letsencrypt-backend if is_letsencrypt
redirect scheme https code 301 if !{ ssl_fc } !is_letsencrypt
frontend
sagt, dass jetzt ein neues Frontend anfängt. http-in
ist der frei wählbare Name des Frontends. Der Name ist eigentlich nur wichtig für die Logdateien. bind :::80 v4v6
sagt, dass dieses Frontend Port 80 auf allen ipv4 und ipv6 adressen dieses Computers öffnen soll. acl regelname regel
legt eine neue Regel an, die später im Zusammenhang mit if
Anweisungen benutzt werden kann. In diesem Fall ist die Regel is_letsencrypt
erfüllt, wenn der Pfad mit /.well-known/acme-challenge/
anfängt. Die use_backend
-Zeile sorgt dafür, dass das Backend mit dem Namen letsencrypt-backend
benutzt wird, wenn die Regel is_letsencrypt
erfüllt ist. In allen anderen Fällen tritt die redirect
-Anweisung in Kraft. Die liefert dem anfragenden Browser eine Antwort mit einem HTTP-Fehlercode zurück, der dem Browser sagt, er soll es mit der verschlüsselten Variante (https://
) versuchen.
Was hier absichtlich fehlt sind weitere use_backend
-Angaben: Damit kann eine unverschlüsselte Anfrage an Port 80 nichts schlimmes anrichten, denn hier wird weder verschlüsselt noch authentifiziert.
frontend https-in
bind :::443 v4v6 ssl crt /etc/haproxy/certs/radi.invisibletower.de.pem
reqadd X-Forwarded-Proto:\ https
option forwardfor
acl myvaliduser http_auth(L1)
use_backend letsencrypt-backend if { path_beg /.well-known/acme-challenge/ }
use_backend webcam if myvaliduser { path_beg /webcam/ }
use_backend octoprint if myvaliduser { path_beg /octoprint/ }
use_backend octoprint_socket if myvaliduser { path_beg /sockjs/ }
use_backend askforauth if !myvaliduser
Das nächste Frontend, diesmal lauscht es auf Port 443 und macht eine SSL-Verschlüsselung mit dem angegebenen privaten Zertifikat. reqadd X-Forwarded-Proto:\ https
fügt dem HTTP-Request einen X-Forwarded-Proto
Header hinzu. Welche Bedeutung der genau hat muss man googlen. (PS: Der Schrägstrich escapet das Leerzeichen). option forwardfor
fügt ebenfalls irgendeinen (wichtigen?) Header hinzu. acl myvaliduser http_auth(L1)
legt eine neue Regel mit dem frei wählbaren Namen myvaliduser
an. Die Regel ist erfüllt, wenn http_auth(L1)
stimmt. http_auth
schaut sich die Request-Header an, sucht nach einer Username:Passwort-Kombination und vergleicht die mit der Liste L1
. Die use_backend
Zeilen nutzen das Ergebnis dieser Überprüfung für ihre Entscheidung, an welches Backend sie den Request weiterleiten sollen. Das Backend askforauth
bekommt alle Anfragen mit falschem oder fehlendem Passwort (das ! bedeutet Negation). Die Regeln können übrigens entweder über eine acl
-Anweisung angegeben werden oder innerhalb von geschweiften Klammern { .. }
nach dem if stehen. Zwischen den einzelnen Regeln braucht nur ein Leerzeichen zu stehen, sie werden behandelt, als würde dazwischen ein logisches UND stehen.
Das Passwort wird zwar über die Leitung geschickt, ist aber wegen der SSL-Verschlüsselung für alle anderen verschlüsselt. Damit niemand beispielsweise in einem öffentlichen WLAN das Passwort abgreifen kann, sollte man am besten keine Authentifizierung im http-in
-Frontend einfordern, nur wie hier im https-in
-Frontend.
backend webcam
reqrep ^([^\ :]*)\ /webcam/(.*) \1\ /\2
server webcam1 127.0.0.1:8080
Dieses Backend leitet alle Anfragen an den Server weiter, der lokal auf Port 8080 lauscht. (127.0.0.1
ist immer die IP-Adresse des Servers selbst). webcam1
ist wieder ein frei wählbarer Name. Die Zeile mit reqrep schneidet den /webcam/
Teil des Pfades aus der Anfrage heraus.
backend askforauth
http-request auth realm Hallo
Dieses Backend antwortet fordert mit einem speziellen HTTP-Response-Header eine Username:Passwort-Kombination vom Browser an.
userlist L1
group G1
....
Ist die Liste der erlaubten User. Selbsterklärend.
Mit diesem Wissen sollte man jetzt selbst weitere Backends anlegen können.
Hat man die Konfiguration geändert, kann man haproxy so neustarten:
$ sudo service haproxy stop
$ sudo service haproxy start
$ sudo service haproxy status
Mit $ journalctl -xe
kann man sich mehr Details zu Fehlern der Konfigurationsdatei anschauen.
Let’s Encrypt 2
Jetzt wo der Proxy steht, kann man die regelmäßige Let’s Encrypt Zertifikatserneuerung auch über den Proxy abwickeln. Auch diese Anleitung ist schamlos abgeschrieben.
Dazu erstellen wir einen Cronjob, der jede Nacht $ certbot renew --deploy-hook letsencrypt_renewhook.sh
aufruft, welches bei erfolgtem Renewal das Script letsencrypt_renewhook.sh
aufruft, welches wiederum die neuen Zertifikate für haproxy zusammenkopiert.
Als erstes erstellen wir das Script mit $ sudo nano ~/domain/letsencrypt_renewhook.sh
:
#!/bin/sh
SITE=radi.invisibletower.de
# move to the correct let's encrypt directory
cd /etc/letsencrypt/live/$SITE
# cat files to make combined .pem for haproxy
cat fullchain.pem privkey.pem > /etc/haproxy/certs/$SITE.pem
# reload haproxy
service haproxy reload
Jetzt noch das Script ausführbar machen: $ sudo chmod u+x ~/domain/letsencrypt_renewhook.sh
und mit $ sudo /home/pi/domain/letsencrypt_renewhook.sh
testen. Es sollte ohne Fehler durchlaufen.
Jetzt muss noch certbot so eingestellt werden, dass er hinter dem haproxy läuft. Dazu öffnen wir die Konfigurationsdatei mit $ sudo nano /etc/letsencrypt/renewal/radi.invisibletower.de.conf
und fügen in die Kategorie [renewalparams]
folgende Einstellung ein:
http01_port = 54321
Den Prozess kann testen wir mit $ sudo certbot renew --dry-run
. Es sollte kein roter Fehlertext auftauchen.
Mit $ sudo crontab -e
öffnen wir die Tabelle aller Cronjobs und fügen ans Ende folgende Zeile ein:
30 2 * * * /home/pi/domain/certbot-auto renew --deploy-hook "/home/pi/domain/letsencrypt_renewhook.sh" >> /var/log/le-renewal.log
Android
Jetzt hatte ich noch das Problem, dass mein Handy die Seite nicht aufrufen wollte (Congstar im Telekom-Netz). Das Problem war bzw. ist, dass standardmäßig kein IPv6 unterstützt wird. Sagt man zumindest im Congstar-Forum. Aber:
Gehe im Handy in die Einstellungen. Dann nach Mobilfunknetze
(das befindet sich bei mir unter Drahtlose Netzwerke => Mehr
). Dann nach Zugangspunkte
und wähle den aktiven Zugangspunkt aus. In der Liste ganz nach unten scrollen und APN-Protokoll
sowie APN-Roaming-Protokoll
auf IPv4/IPv6
stellen. Eventuell kurz den Flugzeugmodus an- und wieder ausschalten. Tadaa.
Sicherheit
Die Konfiguration hat jetzt folgende Probleme:
– Aus dem internen Netzwerk sind die „Unterserver“ auch auf ihren Spezialports zu erreichen. Das ist nicht unbedingt schlimm. Abhilfe würde dieser Befehl schaffen:
$ sudo apt-get install iptables-persistent
$ PORT=8080 sudo /sbin/iptables -A INPUT -p tcp -i wlan0 ! -s 127.0.0.1 --dport ${PORT} -j DROP && sudo /sbin/ip6tables -A INPUT -p tcp -i wlan0 ! -s ::1 --dport ${PORT} -j DROP && sudo /sbin/iptables-save | sudo tee /etc/iptables/rules.v4 && sudo /sbin/ip6tables-save | sudo tee /etc/iptables/rules.v6
– Wer die Dateien certbot-auto, dyndns_update.sh oder letsencrypt_renewhook.sh editiert hat wegen des cronjobs automatisch root-Rechte, auch wenn man ein Passwort für root vergibt. Abhilfe schafft:
$ sudo chown -R root:root ~/domain
$ sudo chmod -R 755 ~/domain
Fazit
So, jetzt sind alle Informationen mal an einer Stelle zusammengefasst. Fehler in der Anleitung bitte in die Kommentare.
Daniel.