Schlagwort-Archive: Seafile

Protokoll zum Ausfall eines Webdienstes nach der fehlgeschlagenen Erneuerung eines Let’s Encrypt Zertifikats

In diesem Beitrag werden die Ursache und der Verlauf der Störung protokolliert, welche die vorübergehende Nichterreichbarkeit einer Seafile1-Installation zur Folge hatte, welche ich für den Freifunk Lippe2 betreibe.

Beschreibung der Umgebung

Die Seafile-Installation wird ausführlich in „Installation von Seafile auf einem Ubuntu/Debian Server“ beschrieben. Die aktuelle Installation wurde wie dort erwähnt zusätzlich mit HSTS (TLS-Kochbuch, Abschnitt 2.7) und HPKP (TLS-Kochbuch, Abschnitt 2.8) gesichert. Das benötigte TLS-Zertifikat stammt von Let’s Encrypt (Abschnitt 3.4.1, TLS-Kochbuch).

Zur automatisierten Verlängerung wird das Skript smartrenew.sh verwendet, welches in Abschnitt 5.4 im TLS-Kochbuch beschrieben wird.

Eingang der Störungsmeldung

Die Störungsmeldung ging am 04.02.2017 per E-Mail. Die Meldung wies auf einen Fehler bei der Erneuerung des Zertifikats durch Let’s Encrypt hin:


arsing account key...
Parsing CSR...
Registering account...
Already registered!
Verifying seafile.example.com...
Traceback (most recent call last):
  File "acme-tiny-by-frezbo/acme_tiny.py", line 200, in 
    main(sys.argv[1:])
  File "acme-tiny-by-frezbo/acme_tiny.py", line 196, in main
    signed_crt = get_crt(args.account_key, args.csr, args.acme_dir, verifychallenge=args.verifychallenge, log=LOGGER, CA=args.ca)
  File "acme-tiny-by-frezbo/acme_tiny.py", line 150, in get_crt
    domain, challenge_status))
ValueError: seafile.example.com challenge did not pass: {u'status': u'invalid', u'validationRecord': [{u'url': u'http://seafile.example.com/.well-known/acme-challenge/ag0bYKs4XzfkYlzRPYLC1cFtv-ypEIQCEVfYulPlMGk', u'hostname': u'seafile.example.com', u'addressUsed': u'IPv4-Adresse', u'port': u'80', u'addressesResolved': [u'IPv4-Adresse', u'IPv6-Adresse']}, {u'url': u'https://seafile.example.com/.well-known/acme-challenge/ag0bYKs4XzfkYlzRPYLC1cFtv-ypEIQCEVfYulPlMGk', u'hostname': u'seafile.example.com', u'addressUsed': u'IPv4-Adresse', u'port': u'443', u'addressesResolved': [u'IPv4-Adresse', u'IPv6-Adresse']}], u'keyAuthorization': u'ag0bYKs4XzfkYlzRPYLC1cFtv-ypEIQCEVfYulPlMGk.pW0Gip0L_WLx7XEnmH_3ZArt9Vi4TbokaUuSXsc1dm4', u'uri': u'https://acme-v01.api.letsencrypt.org/acme/challenge/u5hporNk0I5YunYlQQeJPKdRyd-9BKGMWjBhm_7Za9E/578797992', u'token': u'ag0bYKs4XzfkYlzRPYLC1cFtv-ypEIQCEVfYulPlMGk', u'error': {u'status': 403, u'type': u'urn:acme:error:
 unauthorized', u'detail': u'Invalid response from http://seafile.example.com/.well-known/acme-challenge/ag0bYKs4XzfkYlzRPYLC1cFtv-ypEIQCEVfYulPlMGk: "\r\n404 Not Found\r\n\r\n404 Not Found\r\n"'}, u'type': u'http-01'}
 * Reloading nginx configuration nginx
   ...fail!

Der Fehlermeldung ist zu entnehmen, dass die ACME-Challenge nicht verifiziert werden konnte, da der Server auf die Anfrage mit der Meldung „404 Not Found“ antwortete. Dieser Fehler konnte durch Aufruf der URL mit dem Programm wget bestätigt werden.

Ergebnis der Systemanalyse

Die Analyse der NGINX-Konfiguration ergab, dass der von Let’s Encrypt signierte öffentliche Schlüssel des Zertifikats nicht zum dazugehörenden privaten Schlüssel passte. Dies hatte zur Folge, dass sich die Webseite im Browser, der HSTS und HPKP unterstützt, nicht mehr aufgerufen werden konnte. Denn durch HSTS wird auch ein Aufruf der Seite über HTTP automatisch auf HTTPS umgeleitet. Hier wird nun durch HPKP ebenfalls festgestellt, dass der gepinnte Key nicht mit dem vom Webserver ausgelieferten übereinstimmte und der Browser verweigerte den Aufruf der Webseite.

Die Ursache hierfür lag in der fehlenden Fehlerbehandlung im folgenden Skript:


python acme-tiny-by-frezbo/acme_tiny.py --no-verify --account-key /var/www/seafile.example.com/ssl/account.key --csr /var/www/seafile.example.com/ssl/seafile.example.com.csr --acme-dir /var/www/seafile.example.com/public/.well-known/acme-challenge/ > /var/www/seafile.example.com/ssl/seafile.example.com.crt

cat /var/www/seafile.example.com/ssl/seafile.example.com.crt /var/www/seafile.example.com/ssl/lets-encrypt-x3-cross-signed.pem > /var/www/seafile.example.com/ssl/seafile.example.com_chained.crt

sudo service nginx reload

Zuerst versucht das Skript, das Zertifikat zu erneuern und in die Datei seafile.example.com.crt zu schreiben. Schlägt dieser Vorgang fehl, wird durch den zweiten Teil des Skripts trotzdem aus der korrupten Datei und dem Intermediate-Zertifikat die Zertifikatskette erstellt und in der Datei seafile.example.com_chained.crt gespeichert.

In diesem Fall trat bei der Erneuerung des Zertifikats ein Fehler auf. Es wurde jedoch trotzdem eine Datei namens seafile.example.com.crt mit einer Größe von 0 Byte erstellt. Daher fehlte das Zertifikat auch in der daraufhin erstellten Zertifikatskette. Dies hatte zur Folge, dass durch HPKP der Webbrowser den Zugriff auf die Domain verweigerte und die Anwendung nicht mehr erreichbar war.

Eine Überprüfung der Datensicherung ergab leider, dass das Verzeichnis mit den Zertifikatsdateien nicht Bestandteil des Backups war. Somit war die schnelle Wiederherstellung der Zertifikatskette nicht möglich.

Störungsbeseitigung

Um die Störung zu beseitigen, wurde zuerst die bestehende vHost-Konfiguration für die Seafile-Instanz deaktiviert. Anschließend wurde ein neuer vHost eingerichtet, der ausschließlich via HTTP das Verzeichnis ausliefert, welches die ACME-Challenge beinhaltet:


server {
        listen       80;
        server_name  seafile.example.com;

        root /var/www/seafile.example.com/public/;
        location / {
        index index.html index.htm index.php;
        }
}

Nun wurde das Skript zur Erneuerung des Let’s Encrypt Zertifikats erneut gestartet. Das Skript lief diesmal fehlerfrei durch und es wurde wieder eine vollständige Zertifikatskette erstellt und in der vorgesehenen Datei gespeichert.

Daher konnte die temporäre vHost-Konfiguration nun wieder deaktiviert und die ursprüngliche Konfiguration wieder aktiviert werden.

Maßnahmen zur Risikominimierung

Nachdem mich die Störung natürlich zur ungünstigsten Zeit (welche Zeit ist für eine Störung schon günstig?) erwischt hat, habe ich mir Gedanken gemacht, wie sich das Risiko minimieren lässt, dass mich der gleiche Ärger in Zukunft nochmal ereilt.

Datensicherung anpassen

Zuerst habe ich meine Datensicherung angepasst und das Verzeichnis, welches die Zertifikatsdateien enthält, mit ins Backup aufgenommen. Selbstverständlich ist das Backup nur die halbe Miete. Ob sich dieses erfolgreich wiederherstellen lässt, muss natürlich auch getestet werden.

Fehler bei der Skript-Ausführung abfangen

Wie weiter oben bereits beschrieben, begann die ganze Problematik damit, dass das Skript zur Zertifikatserneuerung weiterarbeitete, obwohl bereits bei der Erneuerung des Zertifikats ein Fehler aufgetreten war.

Zukünftig soll das Skript abbrechen, wenn bei der Verarbeitung ein Fehler auftritt. Dazu wurde es wie folgt erweitert:


function check() {
  if [ $1 -gt 0 ]; then
    echo "Uuups, hier ist was schiefgegangen"
    echo "exit $1"
    exit 1
  fi
}

python acme-tiny-by-frezbo/acme_tiny.py --no-verify --account-key /var/www/seafile.example.com/ssl/account.key --csr /var/www/seafile.example.com/ssl/seafile.example.com.csr --acme-dir /var/www/seafile.example.com/public/.well-known/acme-challenge/ > /var/www/seafile.example.com/ssl/seafile.example.com.crt

check $?

cat /var/www/seafile.example.com/ssl/seafile.example.com.crt /var/www/seafile.example.com/ssl/lets-encrypt-x3-cross-signed.pem > /var/www/seafile.example.com/ssl/seafile.example.com_chained.crt

check $?

sudo service nginx reload

Schlägt die Erneuerung des Zertifikats fehl, gibt das Kommando einen Exit-Status größer Null zurück. Dies wird von der check-Funktion erkannt und das Skript abgebrochen. Dadurch bleiben die alte Datei mit der Zertifikatskette erhalten und der Dienst bleibt verfügbar, bis das Problem mit der Zertifikatserneuerung behoben werden kann.

Installation von Seafile auf einem Ubuntu/Debian Server

Diese Anleitung beschreibt, wie die Software Seafile1 auf einem Ubuntu Server installiert werden kann. Sie gilt prinzipiell auch für Debian. Jedoch müssen einzelne Schritte ggf. angepasst werden.

Getestete Versionen

BetriebssystemUbuntu 14.04 LTS Server
WebserverNGINX 1.4.6
DatenbankserverMySQL 5.6
Seafile5.1.4 64bit

Einleitung

Seafile ist eine freie Software, um Dateien zentral auf einem eigenen Server zu speichern (Filehosting). Benutzer können auf ihre Daten über eine Webschnittstelle zugreifen oder über Desktop-Clients synchronisieren. Quelle: Wikipedia {de}

Seafile2 bildet damit einen Teil des Funktionsumfangs bekannter Open Source Projekte wie ownCloud und Nextcloud ab, konzentriert sich dabei jedoch auf die „Sync+Share“-Funktion für Dateien. Jan Karres hat in seinem Blog einen schönen Artikel über den Vergleich ownCloud vs Seafile geschrieben, welcher die Eigenschaften sowie die Vor- und Nachteile beider Projekte erläutert.

Bei dem hier vorliegenden Anwendungsfall habe ich mich für Seafile entschieden, da die einzige Anforderung im Sync+Share von Dateien besteht. Seafile bietet im Gegensatz zu ownCloud auf diesem Gebiet auch clientseitige Verschlüsselung3 an. Die zukünftigen Anwender sind im Umgang bereits geübt und die Sync-Clients bereits auf den meisten ihrer Endgeräte installiert.

Um dieser Anleitung folgen zu können, werden grundlegende Kenntnisse in der Administration eines Linuxservers vorausgesetzt.

Anleitung

Die folgende Anleitung wurde auf Basis der Artikel von Jan Karres4 und André P.5 erstellt.

Voraussetzungen

Folgende Dienste müssen auf dem Server installiert und funktionsfähig sein, um Seafile installieren zu können:

  • NGINX Server in Grundkonfiguration6
  • MySQL Datenbank7
  • Erreichbarkeit unter der Domain „seafile.domain.tld“
  • Optional: TLS-Zertifikat für die Domain „seafile.domain.tld“8

Als Servername wird im Folgenden stets seafile verwendet. Dieser ist an den gewünschten Servernamen der eigenen Installation anzupassen.

Installation und Konfiguration

Zuerst werden die von Seafile benötigten Abhängigkeiten installiert:

sudo apt-get install python2.7 python-setuptools python-simplejson python-imaging python-mysqldb

Im zweiten Schritt wird ein Systembenutzer9 erstellt, dessen Home-Verzeichnis die gesamte Seafile-Installation aufnehmen wird. Der Benutzername kann dabei frei gewählt werden.

sudo adduser --system --group BENUTZERNAME

Mit den folgenden Befehlen wird der Benutzer gewechselt, die benötigte Verzeichnisstruktur erstellt, die Software heruntergeladen und entpackt.

# Wechselt zum erstellten Systembenutzer für die Seafile-Installation
sudo su - BENUTZERNAME -s /bin/bash

# Erstellen der Verzeichnisstruktur
mkdir -p seafile/installed && cd seafile/installed
wget wget https://download.seafile.de/seafile-server_latest_x86-64.tar.gz

# Archiv in das Zielverzeichnis entpacken und zurück ins Home-Verzeichnis wechseln
tar -xzvf *tar.gz -C ../ && cd ..

Im Home-Verzeichnis sollte die Verzeichnisstruktur nun wie folgt aussehen:

seafile@rs212997:/home/seafile/seafilesrv$ tree -L 2
.
├── installed
│   └── seafile-server_latest_x86-64.tar.gz
└── seafile-server-5.1.3
    ├── check_init_admin.py
    ├── reset-admin.sh
    ├── runtime
    ├── seaf-fsck.sh
    ├── seaf-fuse.sh
    ├── seaf-gc.sh
    ├── seafile
    ├── seafile.sh
    ├── seahub
    ├── seahub.sh
    ├── setup-seafile-mysql.py
    ├── setup-seafile-mysql.sh
    ├── setup-seafile.sh
    └── upgrade

6 directories, 11 files

Nun wechselt man in das Verzeichnis mit der Software (in diesem Fall seafile-server-5.1.3) und führt das Skript setup-seafile-mysql.sh aus. Die Antworten auf die Fragen sind selbstverständlich an die eigene Umgebung anzupassen.

./setup-seafile-mysql.sh 
Checking python on this machine ...
  Checking python module: setuptools ... Done.
  Checking python module: python-imaging ... Done.
  Checking python module: python-mysqldb ... Done.

-----------------------------------------------------------------
This script will guide you to setup your seafile server using MySQL.
Make sure you have read seafile server manual at

        https://github.com/haiwen/seafile/wiki

Press ENTER to continue
-----------------------------------------------------------------


What is the name of the server? It will be displayed on the client.
3 - 15 letters or digits
[ server name ] seafile

What is the ip or domain of the server?
For example: www.mycompany.com, 192.168.1.101
[ This server's ip or domain ] seafile.domain.tld

Where do you want to put your seafile data?
Please use a volume with enough free space
[ default "/home/BENUTZERNAME/seafilesrv/seafile-data" ] 

Which port do you want to use for the seafile fileserver?
[ default "8082" ] 

-------------------------------------------------------
Please choose a way to initialize seafile databases:
-------------------------------------------------------

[1] Create new ccnet/seafile/seahub databases
[2] Use existing ccnet/seafile/seahub databases

[ 1 or 2 ] 1

What is the host of mysql server?
[ default "localhost" ] 

What is the port of mysql server?
[ default "3306" ] 

What is the password of the mysql root user?
[ root password ] 

verifying password of user root ...  done

Enter the name for mysql user of seafile. It would be created if not exists.
[ default "root" ] seafile_user

Enter the password for mysql user "seafile_user":
[ password for seafile_user ] 

Enter the database name for ccnet-server:
[ default "ccnet-db" ] my-ccnet-db

Enter the database name for seafile-server:
[ default "seafile-db" ] my-seafile-db

Enter the database name for seahub:
[ default "seahub-db" ] my-seahub-db

---------------------------------
This is your configuration
---------------------------------

    server name:            seafile
    server ip/domain:       seafile.domain.tld

    seafile data dir:       /home/BENUTZERNAME/seafilesrv/seafile-data
    fileserver port:        8082

    database:               create new
    ccnet database:         my-ccnet-db
    seafile database:       my-seafile-db
    seahub database:        my-seahub-db
    database user:          seafile_user



---------------------------------
Press ENTER to continue, or Ctrl-C to abort
---------------------------------

Generating ccnet configuration ...

done
Successly create configuration dir /home/BENUTZERNAME/seafilesrv/ccnet.
Generating seafile configuration ...

Done.
done
Generating seahub configuration ...

----------------------------------------
Now creating seahub database tables ...

----------------------------------------

creating seafile-server-latest symbolic link ...  done




-----------------------------------------------------------------
Your seafile server configuration has been finished successfully.
-----------------------------------------------------------------

run seafile server:     ./seafile.sh { start | stop | restart }
run seahub  server:     ./seahub.sh  { start  | stop | restart  }

-----------------------------------------------------------------
If you are behind a firewall, remember to allow input/output of these tcp ports:
-----------------------------------------------------------------

port of seafile fileserver:   8082
port of seahub:               8000

When problems occur, Refer to

        https://github.com/haiwen/seafile/wiki

for information.

Möchte man Seafile via HTTPS nutzen, sind noch zwei Dateien anzupassen. Zuerst wird in die Datei ~/seafilesrv/conf/ccnet.conf der Parameter SERVICE_URL wie folgt angepasst:

SERVICE_URL = https://seafile.domain.tld:8000

Die zweite zu bearbeitende Datei ist seahub_settings.py. Hier wird am Ende der Datei die folgende Zeile eingefügt:

FILE_SERVER_ROOT = 'https://seafile.domain.tld/seafhttp'

Durch manuelles Starten der Dienste wird der Administrator-Account erstellt. Das folgende Listing zeigt die auszuführenden Befehle und Abfragen:

:~$ ~/seafilesrv/seafile-server-latest/seafile.sh start

[09/02/16 21:56:00] ../common/session.c(132): using config file /home/BENUTZERNAME/seafilesrv/conf/ccnet.conf
Starting seafile server, please wait ...
Seafile server started

Done.
:~$ ~/seafilesrv/seafile-server-latest/seahub.sh start-fastcgi

LC_ALL is not set in ENV, set to en_US.UTF-8
Starting seahub (fastcgi) at 127.0.0.1:8000 ...

----------------------------------------
It's the first time you start the seafile server. Now let's create the admin account
----------------------------------------

What is the email for the admin account?
[ admin email ] EURE E-MAIL-ADRESSE

What is the password for the admin account?
[ admin password ] SICHERESPASSWORThttp://start.ubuntu.com/16.04/Google/?sourceid=hphttp://start.ubuntu.com/16.04/Google/?sourceid=hp

Enter the password again:
[ admin password again ] SICHERESPASSWORT



----------------------------------------
Successfully created seafile admin
----------------------------------------




Seahub is started

Done.

Zum Abschluss werden die manuell gestarteten Dienste wieder beendet:

:~$ ~/seafilesrv/seafile-server-latest/seafile.sh stop
:~$ ~/seafilesrv/seafile-server-latest/seahub.sh stop

Um sicherzustellen, dass der lokale Seafile-Webdienst nicht aus dem Internet erreichbar ist, sollte folgender Block in der Datei ~/seafilesrv/conf/seafile.conf vorhanden sein:

[fileserver]
host=127.0.0.1
port=8082

An dieser Stelle wird die aktuelle Shell mit exit verlassen. Von nun an kann mit sudo oder als root weitergearbeitet werden.

Konfiguration von NGINX

Für NGINX wird ein neuer VirtualHost für die Domain seafile.domain.tld erstellt. Wird die Konfiguration aus folgendem Listing übernommen, so sind mindestens die Werte für den root-Pfad, server_name, ssl_certificate und ssl_certificate_key an die eigene Konfiguration anzupassen.

server {
        listen       80;
        server_name  seafile.domain.tld;
        rewrite ^/(.*) https://$server_name/$1 permanent;
}
server {
        listen 443; #IPv4
        #listen [::]:443; # IPv6
        ssl on;
        ssl_certificate /etc/nginx/certs/ssl-unified.crt;
        ssl_certificate_key /etc/nginx/certs/ssl.key;
        server_name seafile.domain.tld;

        location / {
                fastcgi_pass    127.0.0.1:8000;
                fastcgi_param   SCRIPT_FILENAME $document_root$fastcgi_script_name;
                fastcgi_param   PATH_INFO           $fastcgi_script_name;

                fastcgi_param   SERVER_PROTOCOL     $server_protocol;
                fastcgi_param   QUERY_STRING        $query_string;
                fastcgi_param   REQUEST_METHOD      $request_method;
                fastcgi_param   CONTENT_TYPE        $content_type;
                fastcgi_param   CONTENT_LENGTH      $content_length;
                fastcgi_param   SERVER_ADDR         $server_addr;
                fastcgi_param   SERVER_PORT         $server_port;
                fastcgi_param   SERVER_NAME         $server_name;

                fastcgi_param   HTTPS on;
                fastcgi_param   HTTP_SCHEME https;
        }

        location /seafhttp {
                rewrite ^/seafhttp(.*)$ $1 break;
                proxy_pass http://127.0.0.1:8082;
                client_max_body_size 0;
        }

        location /media {
                root /home/BENUTZERNAME/seafilesrv/seafile-server-latest/seahub;
        }
}

Mit der oben stehenden Konfiguration erfolgt automatisch eine Weiterleitung von HTTP auf HTTPS. Wer für die Beantragung und Implementierung des Zertifikats Anleitung benötigt oder die Installation zusätzlich noch mit HSTS und HPKP schützen möchte, sei auf mein TLS-Kochbuch10 verwiesen.

Konfiguration des Seafile-Dienstes

Zur Konfiguration des Dienstes bediene ich mich der Arbeit aus dem Artikel von André P. Dazu wird die Datei /etc/init.d/seafile mit folgendem Inhalt erstellt. Die Parameter user und seafile_dir sind dabei an die eigene Installation anzupassen.

#!/bin/bash

### BEGIN INIT INFO
# Provides:          seafile
# Required-Start:    $local_fs $remote_fs $network
# Required-Stop:     $local_fs
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Starts Seafile and Seahub
# Description:       starts Seafile and Seahub
### END INIT INFO

#### CONFIG START ####
user=seafile
seafile_dir=/srv/seafile/servername
##### CONFIG END #####

script_path=${seafile_dir}/seafile-server-latest
seafile_init_log=${seafile_dir}/logs/seafile.init.log
seahub_init_log=${seafile_dir}/logs/seahub.init.log
[ -d ${seafile_dir}/logs ] || mkdir ${seafile_dir}/logs && chown $user:$user ${seafile_dir}/logs

# Change the value of fastcgi to true if fastcgi is to be used
fastcgi=true
# Set the port of fastcgi, default is 8000. Change it if you need different.
fastcgi_port=8000

case "$1" in
        start)
                sudo -u ${user} ${script_path}/seafile.sh start >> ${seafile_init_log}
                if [  $fastcgi = true ];
                then
                        sudo -u ${user} ${script_path}/seahub.sh start-fastcgi ${fastcgi_port} >> ${seahub_init_log}
                else
                        sudo -u ${user} ${script_path}/seahub.sh start >> ${seahub_init_log}
                fi
        ;;
        restart)
                sudo -u ${user} ${script_path}/seafile.sh restart >> ${seafile_init_log}
                if [  $fastcgi = true ];
                then
                        sudo -u ${user} ${script_path}/seahub.sh restart-fastcgi ${fastcgi_port} >> ${seahub_init_log}
                else
                        sudo -u ${user} ${script_path}/seahub.sh restart >> ${seahub_init_log}
                fi
        ;;
        stop)
                ${script_path}/seafile.sh $1 >> ${seafile_init_log}
                ${script_path}/seahub.sh $1 >> ${seahub_init_log}
        ;;
        *)
                echo "Usage: /etc/init.d/seafile {start|stop|restart}"
                exit 1
        ;;
esac

Mit den folgenden Kommandos wird das Skript ausführbar gemacht, installiert und der Dienst gestartet:

chmod +x /etc/init.d/seafile
update-rc.d seafile defaults
service seafile start

Ende

Die Installation ist damit abgeschlossen. Die Seafile-Installation kann nun über https://seafile.domain.tld aufgerufen und genutzt werden.

An dieser Stelle möchte ich mich noch einmal bei André P. für seinen Artikel „Seafile 4.0.x mit Nginx und MySQL-Backend auf Debian/Ubuntu bedanken. Er stellt die Basis für diese Anleitung mit der aktuellen Version von Seafile dar.

Solltet ihr Seafile mit dieser Anleitung auch unter anderen Versionen von Ubuntu/Debian, etc. installieren können, freue ich mich über eure Kommentare, um die Tabelle mit den getesteten Versionen ergänzen zu können.