Bastian Raschke
Nils Tegetmeyer

Sicherheitskritisch

A blog about IT security and electronics

Sichere Installation und Konfiguration des Kommentarsystems Isso auf Debian

Written by Bastian Raschke.
Published 2015-04-22 in the category Debian Linux.

In einem der letzten Beiträge haben wir berichtet, dass wir unsere Blog-Software erfolgreich umgestellt haben. Dabei wollten wir jedoch die Freiheit eurer Kommentare nicht aus der Hand geben:

Wir wollten vermeiden, einen externen Kommentarsystem-Dienstleister wie beispielsweise Disqus zu nutzen, der theoretisch nach Belieben alles mit den Kommentaren machen könnte. Wir wollten deutschen Datenschutz und volle Freiheit für die Kommentare, und haben uns schlussendlich für die Software Isso des deutschen Entwicklers Martin Zimmermann entschieden, welche das alles bot.

Doch die sichere Einrichtung von Isso auf Debian 7 bot jedoch einige Hürden und Probleme, die wir mit diesem Beitrag genau beschreiben und effizient lösen werden!

Das Kommentarsystem Isso
Das Kommentarsystem Isso

Hinweis: In dieser Anleitung ist stets der Platzhalter "example.net" durch die richtige Domain zu ersetzten.

Vorbereitungen

Zunächst muss ein bisschen Vorarbeit geleistet werden:

Wir legen einen eigenen Systembenutzer für Isso an, da wir den Dienst aus Sicherheitsgründen nicht aus Faulheit etwa als "www-data" oder gar als "root" laufen lassen wollen:

~# adduser --system --group --disabled-password --home /var/cache/isso/ --no-create-home "isso"

Nun wird der Konfigurationsordner und der Datenordner angelegt und bei letzterem der korrekte Ordnereigentümer (unser neuer Systembenutzer "isso") gesetzt:

~# mkdir /etc/isso/
~# mkdir -p /var/lib/isso/database/ /var/lib/isso/virtualenv
~# chown isso:isso /var/lib/isso/database/

Konfiguration und Installation

Und eine Konfigurationsdatei angelegt:

~# nano /etc/isso/example.net.conf

mit folgendem Inhalt:

[general]
# In this section, you configure most comment-related options such as database path, session key and hostname.

# Useful for multiple websites configuration
name = example.net

# File location to the SQLite3 database, highly recommended
# to change this location to a non-temporary location!
dbpath = /var/lib/isso/database/isso.sqlite

# Log file path
log-file = /var/log/isso.log

# URL to your website. When you start Isso, it will probe
# your website with a simple GET / request to see if it can
# reach the webserver. If this fails, Isso may not be able
# check if a web page exists, thus fails to accept new
# comments. You can supply more than one host.
host =
 http://example.net/
 https://example.net/

# Private session key to validate client cookies. If you
# restart the application several times per hour for
# whatever reason, use a fixed key.
# python: binascii.b2a_hex(os.urandom(24))
# session-key =

# Time range that allows users to edit/remove their own
# comments. See https://github.com/posativ/isso/blob/master/docs/CONFIGURATION.rst
# for valid values.
max-age = 30m

## Nofification option
notify = smtp


[moderation]
# Enable moderation queue and handling of comments still in moderation queue

# Enable comment moderation queue. This option only affects
# new comments. Comments in modertion queue are not visible
# to other users until you activate them.
enabled = true

# Remove unprocessed comments in moderation queue after given time.
purge-after = 30d


[server]
# interface to listen on. Isso supports TCP/IP and unix domain sockets:
# UNIX domain socket listen = unix:///tmp/isso.sock
# TCP/IP listen = http:///localhost:1234/
#
# When gevent is available, it is automatically used for http:// Currently,
# gevent can not handle http requests on unix domain socket (see #295 and #299
# for details).  Does not apply for uWSGI.
# listen = http://127.0.0.1:8095

# reload application, when the source code has changed. Useful for development.
# Only works with the internal webserver.
reload = off

# show 10 most time consuming function in Isso after each request.
# Do not use in production.
profile = off


[smtp]
# Isso can notify you on new comments via SMTP. In the email notification, you also can moderate comments.
# If the server connection fails during startup, a null mailer is used.

# self-explanatory, optional
username = BENUTZERNAME

# self-explanatory, optional.
password = PASSWORT

# SMTP server
host = SERVERADRESSE

# SMTP port
port = 587

# use a secure connection to the server, possible values: "none", "starttls"
# or "ssl". Python 2.X probably does not validate certificates (needs
# research). But you should use a dedicated email account anyways.
security = starttls

# recipient address, e.g. your email address
to = EMPFAENGER

# sender address, e.g. isso@example.tld
from = "Isso comment system" <BENUTZERNAME>

# specify a timeout in seconds for blocking operations like the connection attempt.
timeout = 10


[guard]
# Enable basic spam protection features, e.g. rate-limit per IP address (/24 for IPv4, /48 for IPv6).

# enable guard, recommended in production. Not useful for debugging purposes.
enabled = true

# limit to N new comments per minute.
ratelimit = 2

# how many comments directly to the thread
# (prevent a simple while true; do curl ...; done.
direct-reply = 3

# allow commenters to reply to their own comments when they could still edit the
# comment. After the editing timeframe is gone, commenters can reply to their
# own comments anyways. Do not forget to configure the client.
reply-to-self = false

Noch ein paar ergänzende Erklärungen für wichtige Einstellungen:

  • Die Einstellung host kann einen oder mehrere Werte aufnehmen und legt fest, für welche Domains sich Isso zuständig fühlt. Wenn die korrekte Domain nicht aufgeführt ist, dann wird Isso dort nicht funktionieren. Wichtig ist also, dass ihr eure Domain als HTTP und HTTPS (wenn vorhanden) aufführt!

  • Wir nutzen die Einstellung listen nicht, da Isso nicht eigenständig auf einer Schnittstelle hören soll, sondern diese Aufgabe später Gunicorn übernimmt. Lediglich gegebenenfalls zum Testen ohne Gunicorn sinnvoll.

  • Da wir die Einstellung notify = smtp gewählt haben, müssen wir gültige SMTP-Daten eingeben, über den die Benachrichtungs-E-Mails versendet werden. Die Einstellung username sollte in den meisten Fällen die komplette E-Mail-Adresse sein oder nur der Teil vor dem @. Der host ist die Serveradresse eures Providers, z.B. smtp.provider.tld. Je nach dem, was euer Provider unterstützt, müsst ihr einen korrekten port wählen und auch die Verbindungsverschlüsselung mit der Einstellung security anpassen (es ist dringend zu empfehlen, eine Verschlüsselung zu wählen, damit die E-Mails nicht komplett im Klartext durch das Internet gehen). Bei der Einstellung to tragt ihr die E-Mail-Adresse des Empfängers ein. Die Einstellung from (Absender-E-Mail-Adresse) muss in den meisten Fällen eure wirkliche Adresse enthalten, die ihr bei username eingetragen habt - ansonsten liefern viele Provider die E-Mails nicht aus (z.B. web.de).

Weitere Einstellungen sind meiner Meinung nach selbsterklärend (speziell mit den gegebenen Kommentaren). Bei Bedarf kann natürlich trotzdem gefragt werden ;-)

Virtuelle Python-Umgebung anlegen

Nun werden noch zwei Pakete installiert. Ersteres ist zum Erstellen und Verwalten von sogenannten virtuellen Python-Umgebungen nötig, und letzteres ist erforderlich, um bei späteren Kompilierungen unschöne Fehlermeldungen á la "fatal error: Python.h: Datei oder Verzeichnis nicht gefunden compilation terminated. error: command 'gcc' failed with exit status 1" zu vermeiden:

~# apt-get install python-virtualenv python-dev

Daraufhin können wir die virtuelle Umgebung erstellen,

~# virtualenv /var/lib/isso/virtualenv/

die virtuelle Umgebung aktivieren (dieser Schritt ist immer nötig, wenn man an der Umgebung arbeiten möchte),

~# cd /var/lib/isso/virtualenv/
~# source ./bin/activate

und die Software Isso über PIP installieren:

~# pip install isso

Warum eine virtuelle Python-Umgebung?

Wir nutzen eine virtuelle Python-Umgebung, damit unser System nicht von dem PIP-Paketmanager "vollgeranzt" wird. PIP ist keinesfalls mit richtigen Paketmanagern wie "apt" oder "pacman" zu vergleichen, da es nicht möglich ist, ein Paket wieder sauber zu entfernen. Und das ist nicht unbedingt ein Zustand, den ich auf meinem sicherheitsoptimierten Server hinterlassen möchte.

Alles was in einem virtuellen Python-Umgebungen passiert, bleibt auch da. Um es restlos zu entfernen, muss lediglich dessen Ordner gelöscht werden und alles ist vergessen ;-)

Gunicorn installieren

Nun müssen wir noch die Software Gunicorn installieren, die für Python-Applikationen eine bequeme und performante Möglichkeit bietet, diese als Web-Dienst laufen zu lassen. Wir nutzen Gunicorn hauptsächlich für den automatischen Start unseres Dienstes:

~# apt-get install gunicorn

Dann löschen wir die Standardkonfigurationen:

~# rm /etc/gunicorn.d/*

Und legen eine neue an:

~# nano /etc/gunicorn.d/isso

mit folgendem Inhalt:

CONFIG = {
    'environment': {
        'ISSO_SETTINGS': '/etc/isso/example.net.conf',
    },

    'working_dir': '/var/lib/isso/virtualenv/',
    'python': '/var/lib/isso/virtualenv/bin/python',

    'user': 'isso',
    'group': 'isso',

    'args': (
        '-b 127.0.0.1:8095',
         'isso.run',
    ),
}

Nun muss noch ein kleiner Umstand behoben werden. Da wir Gunicorn global und nicht in der virtuellen Umgebung installiert haben, müssen wir zwei Verlinkungen korrigieren:

~# ln -s /usr/lib/python2.7/dist-packages/gunicorn ./lib/python2.7/site-packages/gunicorn
~# ln -s /usr/lib/python2.7/dist-packages/gunicorn-0.14.5.egg-info ./lib/python2.7/site-packages/gunicorn-0.14.5.egg-info

Ansonsten resultiert folgende Fehlermeldung beim Start von Gunicorn:

[....] Starting Gunicorn workers: [isso]Traceback (most recent call last):
  File "/usr/bin/gunicorn", line 5, in <module>
    from pkg_resources import load_entry_point
  File "/var/lib/isso/virtualenv/local/lib/python2.7/site-packages/distribute-0.6.24-py2.7.egg/pkg_resources.py", line 2707, in <module>
    working_set.require(__requires__)
  File "/var/lib/isso/virtualenv/local/lib/python2.7/site-packages/distribute-0.6.24-py2.7.egg/pkg_resources.py", line 686, in require
    needed = self.resolve(parse_requirements(requirements))
  File "/var/lib/isso/virtualenv/local/lib/python2.7/site-packages/distribute-0.6.24-py2.7.egg/pkg_resources.py", line 584, in resolve
    raise DistributionNotFound(req)
pkg_resources.DistributionNotFound: gunicorn==0.14.5
Traceback (most recent call last):
  File "/usr/sbin/gunicorn-debian", line 218, in <module>
    sys.exit(main())
  File "/usr/sbin/gunicorn-debian", line 100, in main
    getattr(config, action)()
  File "/usr/sbin/gunicorn-debian", line 188, in start
    self.check_call(args + gunicorn_args + self['args'], env=env)
  File "/usr/sbin/gunicorn-debian", line 157, in check_call
    subprocess.check_call(*args, **kwargs)
  File "/usr/lib/python2.7/subprocess.py", line 511, in check_call
    raise CalledProcessError(retcode, cmd)
subprocess.CalledProcessError: Command '['start-stop-daemon', '--start', '--oknodo', '--quiet', '--chdir', '/var/lib/isso/virtualenv/', '--pidfile', '/var/run/gunicorn/isso.pid', '--exec', '/var/lib/isso/virtualenv/bin/python', '--', '/usr/bin/gunicorn', '--pid', '/var/run/gunicorn/isso.pid', '--name', 'isso', '--user', 'isso', '--group', 'isso', '--daemon', '--log-file', '/var/log/gunicorn/isso.log', '-b 127.0.0.1:8095', 'isso.run']' returned non-zero exit status 1

Danke an Velmont für die Lösung des Problems. Nach einem Neustart von Gunicorn ist Isso verfügbar:

~# /etc/init.d/gunicorn restart

Dies kann getestet werden, indem im Browser die Adresse http://127.0.0.1:8095 aufgerufen wird. Erscheint dort Bad Request missing uri query, hat alles geklappt - auch wenn es die Fehlermeldung nicht vermuten lässt ;-)

Isso wird durch Gunicorn bereitgestellt
Isso wurde erfolgreich durch Gunicorn bereitgestellt

Nginx konfigurieren

Nun ist noch eine Komponente im System empfehlenswert. Ich habe ab diesem Punkt auch bereits die Stirn gerunzelt, warum der Zirkus nicht mal fertig ist. Isso wird doch schon durch Gunicorn performanter bereitgestellt. Jedoch können wir die Geschichte noch weiter optimieren, indem wir Nginx einsetzen, welcher für hohe Last gedacht ist und uns auch die Möglichkeit bietet, Isso durch SSL bereitzustellen. Und aus diesem Grund setzen wir Nginx noch zustätzlich ein. Folgend findet ihr 2 mögliche Blöcke, die ihr in eure VHost-Konfiguration integrieren könnt:

Konfiguration im eigenen Server-Block (z.B. comments.deinedomain.tld):

location /
{
    proxy_pass http://127.0.0.1:8095;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

Alternative Konfiguration in bestehendem Server-Block (z.B. deinedomain.tld/comments/):

location /comments/
{
    proxy_pass http://127.0.0.1:8095/; ## Important: Notice the slash!
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

Letztere Konfiguration kann sinnvoll sein, wenn ihr Isso über SSL ausliefern wollt, aber kein kostenintensives Wildcard-Zertifikat zur Verfügung habt (wie ich).

Hinweis: Isso benötigt spezielle HTTP-Methoden wie "PUT", "OPTIONS" und "DELETE", welche durch Nginx nicht blockiert sein dürfen! Dies ist aber standardmäßig nicht der Fall, nur wenn man Nginx aktiv leicht paranoid konfiguriert hat.

Nach einem Neustart von Nginx ist dann alles geschafft:

~# /etc/init.d/nginx restart

Und Isso kann durch Nginx erreicht werden.

Weiterer Hinweis

Mir hat während des Entwickelns dieser Anleitung ein Fehler zu schaffen gemacht, der extrem schwer nachvollziehen war, und den ich nur durch viel Analyse gefunden habe:

Web-Browser wie z.B. Firefox und Chrome liefern via Nginx unverständlicherweise keine Cookies an den Server (Isso) zurück (angenommen werden kurioserweise alle), wenn keine richtige Domain, sondern "nur" eine IP-Adresse wie z.B. http://127.0.0.1/ genutzt wird. Dies war sehr nervig beim Testen, da ich mich gewundert habe, dass ich meine eigenen Kommentare z.B. nicht mehr editieren/löschen konnte, weil mein lieber Browser die Cookies nicht ausliefert... Ich habe keine Ahnung, was das soll. Herausgefunden habe ich das nur, weil ich die Requests in der Netzwerk-Analyse exakt verglichen habe. Ein Workaround war es, z.B. via der Hosts-Datei eine Domain für 127.0.0.1 zu nutzen.

Ohne Nginx - die Cookies werden gesendet Mit Nginx - die Cookies werden nicht gesendet
Vergleich: Links (ohne Nginx) werden die Cookies gesendet, rechts (mit Nginx) nicht


Tags: Debian, Sicherheit, Datenschutz

Proudly generated with Pelican - without PHP, Perl, MySQL & Co. Jump to top