WWW-Server

Beim Webserver gibt es sehr viel Optimierungspotential.
Wir werden hier erst mal apache mit einer übersichtlichen und einfach zu erweiternden Konfiguration erstellen und danach suExec mit FastCGI einsetzten um die Sicherheit auch bei mehreren Benutzern auf dem System (sog. „ISP Hosting Setup“) zu gewährleisten.
Damit dann die User ihre Daten auch hochladen können, wird ein FTP-Server (proftpd) installiert.

Danach werden wir noch einen MySQL-Datenbankserver erstellen.

apache Webserver

Zuerst werden mal die notwendigen Komponenten installiert:

yum install ImageMagick unzip mailx httpd mod_fcgid mod_ssl php php-cli php-common php-devel php-gd php-imap php-mbstring php-mcrypt php-mysql php-pdo php-pear
chkconfig httpd on

Gegegebnfalls kann man auf einem Server noch unerwünschte Software entfernen: 😉

yum remove firewalld firewalld-filesystem NetworkManager NetworkManager-libnm

Damit müssten wir alles haben um auch komplexere Applikationen wie typo3 oder gallery2 laufen zu lassen.

Mit dem SCL an neue PHP Versionen kommen

Hat man eine „Enterprise“ Linux Distribution wie RHEL/CentOS bringt das einige Vorteile, wenn es um die Stabilität des Systems geht – So laufen auch alte Webapplikationen ohne Probleme, zumindest so lange man kein Major Release Upgrade macht. Doch das bringt auch Nachteile: Während mehreren Jahren wird beispielsweise PHP nicht updatet. Zwar fliessen Security Patches nach wie vor noch in rein (sog. „Backports“), doch neue Funktionen gibt es keine mehr. Eine kleine Abhilfe gibt es mit dem EPEL-Repository, doch auch da hinken die (etwas neueren) Pakete hinterher. Und neuere Web-Applikation fordern relativ neue PHP Versionen.
Eine Abhilfe gibt es mit dem RedHat Software Collections ( SCL ) Repository. Dies erstellt in /opt/rh/ eine komplett neue Umgebung und stellt dort auch die neuesten PHP Versionen zur Verfügung welche man sogar getrennt von der „alten“ PHP Version benutzen kann. So lässt man die alten Web-Applikationen mit dem alten PHP und neue mit dem neuen laufen.

Installieren kann man dies einfach mit yum:

yum install centos-release-scl

und danach z.B. PHP 7 installieren:

yum install rh-php70

Und dazu die Module:

yum install rh-php70 rh-php70-php-bcmath rh-php70-php-cli rh-php70-php-common rh-php70-php-devel rh-php70-php-gd rh-php70-php-imap rh-php70-php-intl rh-php70-php-mbstring rh-php70-php-mysqlnd rh-php70-php-pdo rh-php70-php-pear rh-php70-php-pecl-apc rh-php70-php-process rh-php70-php-pspell rh-php70-php-xml rh-php70-php-mcrypt

Die Problematik mit mod_php

Bei den meisten apache Webservern läuft PHP als Modul: mod_php. Dies ist sehr einfach einzubinden, allerdings auch sehr suboptimal. Denn dann werden sämtliche PHP Scripte mit dem Benutzernamen und Gruppe des Webservers ausgeführt (z.B. apache). Dies bedingt, dass „apache“ auf alle Web-Verzeichnisse zumindest lese, bei manchen sogar Schreibberechtigung erfordert. -So kann also jeder Benutzer auf dem Server sämtliche Inhalte der anderen Benutzer lesen und viele sogar beschreiben!

Workarounds gibts bei php mit dem sog. „save_mode“, was aber nur eine halbe Lösung ist.

Lässt man hingegen PHP als (fast-)CGI laufen, so werden alle PHP-Skripte mit dem tatsächlichen Web-Benutzer ausgeführt. Dabei gehts es sogar soweit, dass jeder Benutzer ein eigenes PHP und sogar eine eigene php.ini haben könnte.

Deshalb installierten wir oben apache mit der CGI-Variante und suEXEC, anstatt „mod_php“.

FastCGI vs. mpm-itk/mpm-peruser
Neben FastCGI gibt es noch andere Ansätze um vhosts auf unterschiedlichen usern laufen zu lassen, eine davon mpm-itk und mpm-peruser.
Die letzteren beiden mpm-* arbeiten als apache-module und haben den Vorteil, dass die ganze apache-Instanz mit dem jeweiligen user ausgeführt wird; somit kann man mod_php wieder verwenden und hat das Problem auch schon bei anderen Script sprachen wie Perl gelöst; zudem muss man dem apache-user keine Leserechte auf den Web-Verzeichnissen mehr geben.

Diese, auf den ersten blick „elegante“ Lösung hat jedoch einen grossen Haken: Damit der apache die einzelnen vhost-Instanzen als andere user starten kann, muss der Kernprozess selber als root laufen!
Theoretisch könnte somit ein Hacker, falls er eine Sicherheitslücke ausnutzen kann bevor der apache den vhost initiiert (gemäss des Autors von mpm-itk am ehesten bei mod_ssl) root rechte auf dem server erlangen!
Und sollte man einmal vergessen bei einem vhost explizit einen user anzugeben würde dieser vhost ebenfalls mit root-rechten(!) laufen.

Natürlich ist das „Risiko“, dass das passiert sehr klein, mir ist jedoch trotzdem nicht wohl dabei einen Prozess welcher derart nach aussen offen und bekannt ist als root laufen zu lassen. – Es muss letztendlich jeder admin selber bestimmen, was ihm wichtiger ist. Eine Anleitung für mpm-itk gibts im Blog-Beitrag: Hosting absichern: Apache2 mit ITK-MPM.

Ausserdem: Wenn man wirklich auf „highest security“ aus ist, würde man einen reverse-proxy server vor den apache stellen, was zwar die aufwändigste, aber auf jeden Fall die sicherste Lösung ist.

Konfiguration

Als erstes müssen wir nun sicherstellen, dass die Datei: /etc/httpd/conf.d/php.conf entweder leer ist, oder nicht existiert. Ist die Datei da, einfach alles darin löschen und abspeichern.
Alternativ lässt sich das auch mit diesem Kommando machen:

test -f /etc/httpd/conf.d/php.conf && echo "# This module is disabled">/etc/httpd/conf.d/php.conf

Wo das php binary nun liegt finden wir heraus mit:

which php-cgi

Bei Debian und CentOS ist dies normalerweise: /usr/bin/php-cgi.
Nun müssen wir testen ob es sich auch um die fcgi-Version handelt:

/usr/bin/php-cgi -v

Hier müsste so was wie hier ausgegeben werden (siehe das „fcgi“ in den Klammern):

PHP 5.2.10 (cgi-fcgi) (built: Nov 13 2009 11:39:02)
Copyright (c) 1997-2009 The PHP Group
Zend Engine v2.2.0, Copyright (c) 1998-2009 Zend Technologies
Hinweis
Wir könnten nun grundsätzlich php auch selbst kompilieren und dies verwenden. Dabei müsste man einfach darauf achten, dass php mit der fcgi-option kompiliert wird.
Der Nachteil wäre aber dann, dass wir php nicht mehr automatisch mit dem Paketmanager upgraden könnten.

Nun noch überprüfen, ob FCGID aktiviert ist, dazu müsste im file: /etc/httpd/conf.d/fcgid.conf oder in der httpd.conf so etwas stehen:

LoadModule fcgid_module modules/mod_fcgid.so
 
    AddHandler fcgid-script fcg fcgi fpl
    AddHandler fcgid-script php
 
SocketPath run/mod_fcgid
SharememPath run/mod_fcgid/fcgid_shm

Anlegen der Benutzer und Gruppen

Nun werden wir für jeden Benutzer einen Account ohne shell Zugriff, sowie die gemeinsame Benutzergruppe „users“ anlegen und den user apache in die Gruppe des Web-Users aufnehmen.
Es ist hier übersichtlich wenn man pro Domain einen Benutzer macht; ausser man hat Benutzer mit mehreren Domains. Wir legen hier einen Benutzer pro Domain an:

mkdir -v /srv/www
groupadd users
useradd testdomain.tld -G users -c "Test domain user" -d /srv/www/testdomain.tld
usermod -a -G testdomain.tld apache

So erhält jeder Benutzer einen system account in der Form: „domain.tld“ und ist sowohl in seiner eigenen Gruppe („domain.tld“), wie auch in der Zusatzgruppe: „users“.

Hinweis
Die Zusatzgruppe „users“ wird benötigt, wann immer alle Benutzer auf ein Verzeichnis Zugreifen können sollen, z.B. das das PHP-Session Verzeichnis (/var/lib/php/session/) oder auch gemeinsam genutzte Dateien und Verzeichnisse.

Der apache selbst hat den gleichnamigen Benutzernamen und ist in der Gruppe: apache (bei debian systemen: www-data), so wie in den Gruppen aller Web-Benutzer (z.B.: „domain.tld“)

Hinweis
Der apache user muss in den Gruppen aller Web-Benutzer sein, da er sonst die Web-Verzeichnisse der Bentzer gar nicht lösen könnte, was jedoch trotz php-fcgi notwendig ist. Denn php-fcgi gilt nur in PHP-Scripten.

Sicherheitstechnisch ist dies jedoch unbedenklich, da umgekehrt keiner der Web-User in der apache Gruppe ist.

Verzeichnisstruktur

Die Domain Verzeichnisse sind nach domain-tld/subdomain gegliedert, d.h. der Virtualhost: subdomain.domain.tld hätte den document root: /srv/www/domain.tld/subdomain/public_html

Eine Skelettstruktur sieht dann entsprechend so aus:

/srv/www/domain.tld/
`-- www
    |-- log
    |   |-- access_log
    |   |-- combined.log
    |   `-- error_log
    `-- public_html
        |-- index.html

Wir haben hier also alle Homepages im Verzeichnis /srv/www/ und jede Domain hat ein Verzeichnis „domain.tld“, so wie die Unterverzeichnisse: „log“ und „public_html“. – Im Verzeichnis „log“ werden die Logfiles pro (sub-)Domain gespeichert und in „public_html“ die öffentlichen Seiten.

Berechtigungen

Die Berechtigungen werden dann wie folgt gesetzt:

drwx--x--x   root apache             /srv/www/
dr-xr-x--x   domain.tld domain.tld   /srv/www/domain.tld/
dr-xr-x--x   domain.tld domain.tld   /srv/www/domain.tld/www/
dr-xr-x---   domain.tld domain.tld   /srv/www/domain.tld/www/log/
drwxr-x---   domain.tld domain.tld   /srv/www/domain.tld/www/public_html/
-rw-r-----   domain.tld domain.tld   /srv/www/domain.tld/www/public_html/index.php

Dies müsste sicher stellen, dass der User wirklich nur in „seinem“ home-Verzeichnis lesen kann; des weiteren darf er auch dort z.B. sein log-Verzeichnis nicht löschen, da dies vom apache verwaltet wird.
Weiter darf die Gruppe (welche der apache user benutzt) Verzeichnisse innerhalb von /srv/www/ lesen (execute), sich diese aber nicht auflisten lassen (read).

Info: Globales Ausführrecht aufs domain/www Verzeichnis
Das „World-Excecute“ Recht (551) muss nicht unbedingt so gesetzt sein. Möchte man aber später z.B. zentralisierte Tools wie z.B. Log-Analyzer einsetzen kann es nötig werden, dass alle User in die Verzeichnisse wechseln können. Anzeigen lassen kann man sich aber nur mit dem Ausführ-Recht nichts.

Alternativ kann man jedoch auch die ACL Erweiterung des Filesystems benutzen:

setfacl -m u:webstats:x /srv/www/domain.tld /srv/www/domain.tld/www

Dies würde dem User webstats erlauben die Verzeichnisse innerhalb von: /srv/www/domain.tld/www zu erreichen.

Anpassen des Filesystems

suEXEC bedingt standardmässig, dass der PHP-Starter unter /var/www liegt; um dies zu ändern müsste man suEXEC selbst/neu kompilieren, deshalb lassen wir es so.
Hinweis: Es geht dabei nur um das script, dass PHP-CGI startet; wo die Webseiten liegen ist egal!

Zuerst brauchen wir jedoch ein dediziertes Verzeichnis für den Web-User wo die PHP-Session files (server cookies) abgelegt werden können:

mkdir -pv /var/local/lib/php/session/testdomain.tld
chown -v testdomain.tld:testdomain.tld /var/local/lib/php/session/testdomain.tld
chmod -v 750 /var/local/lib/php/session/testdomain.tld
Änderung des Standard-Session Verzeichnisses nötig
Wenn man das PHP Standard-Verzeichnis für sessions nimmt (/var/lib/php/session/), welches die Rechte:

drwxrwx---. 2 root apache /var/lib/php/session/

hat, so muss man dieses Verzeichnis für die gemeinsame Gruppe „users“ lesbar machen:

chown -v root:users /var/lib/php/session/

Da jedoch unterhalb dieses Verzeichnisses jeder Web-User noch sein eigenes Verzeichnis bekommt, ist sichergestellt, dass die Web-User untereinander die Session-Files nicht lesen können.

Nun legen wir das fcgi-verzeichnis an:

mkdir -v /var/www/fcgi

Die Idee ist, dass hier für jeden web-user ein eigens Verzeichnis erstellt wird und ein startscript darin, dass „sein“ php-cgi startet wenn die Webseite dieses Benutzers aufgerufen wird. Es ist dabei wichtig, dass jeder Benutzer ein Verzeichnis hat, auch wenn darin normalerweise nur ein script liegt – Ansonsten verweigert suEXEC aus Sicherheitsgründen den Dienst. Das Verzeichnis muss den Benutzer- und Gruppennamen des Webbenutzer haben.

Nun machen wir das mal für unsere testdomain:

mkdir -v /var/www/fcgi/testdomain.tld

Darin erstellen wir das Starterscript:
/var/www/fcgi/testdomain.tld/php-fcgi.sh

#!/bin/sh
. /var/www/fcgi/php-fcgi-wrapper.inc
exec /usr/bin/php-cgi $OPTIONS

und machen es ausführbar und setzen Benutzer/Rechte:

chmod -v 755 /var/www/fcgi/testdomain.tld/php-fcgi.sh
chown -vR testdomain.tld:testdomain.tld /var/www/fcgi/testdomain.tld

Und damit wir die Konfiguration für alle user zentral ändern können, binden wir noch ein include-file /var/www/fcgi/php-fcgi-wrapper.inc ein:

USERNAME=$(cat /etc/passwd |grep $(whoami) |cut -d: -f1)
WEBROOT="/srv/www/$USERNAME"
BASEDIR="/tmp:/usr/share/php:/usr/share/pear:$WEBROOT"
SESSION_DIR="/var/local/lib/php/session/$USERNAME"
PHPRC=/etc/php.ini
export PHPRC
 
OPTIONS="-d open_basedir=${BASEDIR} -d session.save_path=$SESSION_DIR"

Einzelne Werte können hier pro user überschrieben werden, indem sie unterhalb des include files definiert werden.

Dieses Script ist ein sog. „Wrapper“ und wird nun jeweils gestartet wenn die Webseite des Benutzers zum ersten mal aufgerufen wird.

In diesem Script könnte man nun eine ganze Umgebung konfigurieren: Individuelles php binary, eigene php.ini, Optionen voraussetzen, Umgebungsvariablen definieren., usw.
Wir beschränken uns hier aber auf das Standard php/php.ini und konfigurieren nur das open_basedir für den Benutzer.

Nun braucht der Benutzer aber noch ein Webverzeichnis; diese legen wir unter /srv/www an:

mkdir -v /srv/www
mkdir -pv /srv/www/testdomain.tld/www/{log,public_html}
chown -vR testdomain.tld:testdomain.tld /srv/www/testdomain.tld/
chmod -v 551 /srv/www/testdomain.tld
chmod -v 751 /srv/www/testdomain.tld/www
chmod -v 570 /srv/www/testdomain.tld/www/log
chmod -v 750 /srv/www/testdomain.tld/www/public_html

Zu beachten ist hier, dass die Webverzeichnisse dem Domain-Benutzer und der Domain-Gruppe (zu welcher der Apache-User (apache) gehört) gehören. apache bekommt damit aber nur lese- und ausführ rechte. Dies ist nötig, weil sonst der apache ja selbst nicht mehr auf die Seiten zugreifen könnte. Ein Auslesen fremder Daten per PHP wird so aber unmöglich, da PHP mit der Gruppe: „testdomain.tld“ ausgeführt wird.

Des weiteren sollten wir noch sicherstellen, dass vom Benutzer angelgte Dateien/Verzeichnisse auch die entsprechenden Berechtigungen erhalten, die machen wir mit „umask“:

echo "umask 027" >> /srv/www/testdomain.tld/.bashrc

und dann am besten auch im skel-Verzeichnis; so wird das beim anlegen neuer Benutzer automatisch gesetzt:

echo "umask 027" >> /etc/skel/.bashrc

Apache Konfigurieren

Nun erstellen wir eine saubere apache config und für jede Datei eine Virtualhosts Datei. (Kommentare wurden hier der besseren Unübersichtlichkeit halber bewusst weggelassen)

Bestehende Dateien werden hier überschrieben und überflüssige gelöscht. Zum Schluss sollte das Verzeichnis /etc/httpd etwa so aussehen:

drwxr-xr-x  6 root root 1.0K Feb 23 00:31 .
drwxr-xr-x 66 root root 5.0K Mar  7 19:41 ..
drwxr-xr-x  2 root root 1.0K Feb 26 16:10 conf
drwxr-xr-x  2 root root 1.0K Mar  7 22:45 conf.d
lrwxrwxrwx  1 root root   19 Feb 22 05:09 logs -> ../../var/log/httpd
lrwxrwxrwx  1 root root   29 Feb 22 05:09 modules -> ../../usr/lib64/httpd/modules
lrwxrwxrwx  1 root root   13 Feb 22 05:09 run -> ../../var/run
drwxr-xr-x  2 root root 1.0K Apr 15  2008 ssl
drwxr-xr-x  2 root root 1.0K Mar  3 02:23 vhosts.d

Nun gehts an die Dateien:

/etc/httpd/conf/httpd.conf:
Diese Datei kann man gröstenteils beim Standard belassen; lediglich zu unterst muss noch die Zeile:
Include vhosts.d/*
eingefügt werden:

echo "Include vhosts.d/*" >> /etc/httpd/conf/httpd.conf
httpd.conf mit override-file
Grosse, unübersichtliche Konfigurationsdateien wie die httpd.conf lässt man am besten unverändert. So kann man bei einem release-upgrade die Änderungen sehr gut zurückverfolgen.

Falls man trotzdem mal was an dieser Konfigurationsdatei ändern will, bietet sich der Trick mittels eines „override-files“ an, welches auch z.B. beim CSS-Styling genutzt wird: Man included eine zweite Datei am Schluss, in der man dann alle Optionen die man ändern will einträgt. Diese werden so automatisch „überschrieben“, sollten sie bereits existieren:

echo "Include conf/httpd-override.conf" >> /etc/httpd/conf/httpd.conf
# Hier wird alles konfiguriert, was sonst in der httpd.conf eingetragen werden würde. Beispiel:
DirectoryIndex index.html index.htm index.php index.xhtml

SSL-Konfiguration

Grundkonfiguration

apache ist dafür bekannt, das mod_ssl Modul nicht mit zeitgemässer Konfiguration auszuliefern, deshalb muss diese zuerst ein wenig angepasst werden.
Deshalb sollten in /etc/httpd/conf.d/ssl.conf folgende Zeilen auskommentiert (deaktiviert) werden: SSLProtocol und SSLCipherSuite.
Dann zuunterst, wie schon bei der httpd.conf ein ssl.conf.override einbinden:

Include conf.d/ssl.conf.overrides

Nun die Datei ssl.conf schliessen und eine neue Datei /etc/httpd/conf.d/ssl.conf.overrides Datei mit folgendem Inhalt erstellen:

SSLProtocol                            all -SSLv2 -SSLv3
SSLCipherSuite                         ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS
SSLHonorCipherOrder                    on
 
Header always set Strict-Transport-Security "max-age=31536000;"

Dies deaktivert die unsicheren Protokolle SSLv2/SSlv3 und bietet nur noch „sichere“ ciphers an.
Die letzte Header Anweisung schaltet HSTS ein und bringt damit den Browser des Webseiten-Besuchers dazu beim nächsten Mal automatisch die HTTPS Version der Webseite aufzurufen.

Tipp: Ciphers gelegentlich überprüfen

Da es in bestimmten ciphers immer wieder Sicherheitslücken geben kann, sollte die obige Liste gelegentlich überprüft werden.
Dazu gibt es einen SSL Configuration Generator, welche die aktuelle Konfiguration für verscheiedene Webserver Kombinationen und Versionen erzeugt.

Zertifikate

Nun erstellen wir noch ein eigenes (selbst signiertes) SSL-Zertifikat:

mkdir -pv /usr/local/share/ssl/localhost/{certs,keys}
chmod -v 755 /usr/local/share/ssl/localhost/certs
chmod -v 700 /usr/local/share/ssl/localhost/keys
openssl req -x509 -newkey rsa:2048 -keyout /usr/local/share/ssl/localhost/keys/apache.key -out /usr/local/share/ssl/localhost/certs/apache.crt -days 3650 -nodes
Vom Browser akzeptierte SSL Zertifikate

Mit dem selbst-signierten SSL Zertifikat wird zwar die die Übertragung der Seite verschlüsselt, doch der Besucher bekommt von seinem Browser jeweils eine Warnung, dass das Zertifikat „unsicher“ ist. Dies, weil die die Browser wie Firefox, Chrome und co. nur Zertifikate als „sicher“ einstufen, wenn sie von einer bekannten Stelle signiert sind.

Als Ausweg kann man sich bei letsencrypt.org kostenlos ein Zertifikat ausstellen lassen, welches vom Browser des Besuchers ohne Warnung akzeptiert- und als „sicher“ eingestuft wird.

Hier ein Beispiel für letsencrypt und die Domain example.com:

yum install certbot
mkdir -pv /usr/local/share/ssl/example.com/{certs,keys}/
chmod -v 755 /usr/local/share/ssl/example.com/certs
chmod -v 700 /usr/local/share/ssl/example.com/keys
certbot certonly
ln -sv /etc/letsencrypt/live/example.com/cert.crt /usr/local/share/ssl/example.com/certs/letsencrypt.crt
ln -sv /etc/letsencrypt/live/example.com/privkey.key /usr/local/share/ssl/example.com/keys/letsencrypt.key
systemctl reload httpd

Signiertes Zertifikat mit letsencrypt

Das schnelle Beispiel, welches oben vorgestellt wurde ist gut zum testen. In einem professionellen Webhosting Server will man jedoch etwas mehr Kontrolle haben. 😉 Deshalb hier noch eine erweiterte Variante.

Letsencrypt implementiert zum automatischen generieren von SSL Zertikaten das ACME-Protokoll.
Für dieses stellt letsencrypt mit dem certbot einen eigenen Client zur verfügung, es gibt jedoch auch noch diverse andere, die teils besser geignet sind.
Denn da das „offizielle“ certbot Script möglichst alle Fälle abdecken will ist es ziemlich aufgebläht. Der inoffizielle client acme-tiny ist mit nur ca. 200 Zeilen code besser geignet, da er nur das nötigste enthält und dazu gemacht ist als nicht-root Benutzer zu laufen.

Zuerst wird das Script heruntergeladen:

wget -O - "https://raw.githubusercontent.com/diafygi/acme-tiny/master/acme_tiny.py" > /usr/local/bin/acme_tiny.py
chmod -v 755 /usr/local/bin/acme_tiny.py

Dann wird ein Benutzer erstellt, welcher das ACME-Script ausführt mit entsprechenden Rechten:

useradd -c "letsencrypt ACME client" -d /srv/www/letsencrypt/ letsencrypt
echo "umask 0022" >> /srv/www/letsencrypt/.bashrc
chown -v letsencrypt:apache /srv/www/letsencrypt/
chmod -v 750 /srv/www/letsencrypt/

Damit letsencrypt funktioniert, muss der Client eine Datei im Verzeichnis .well-known/acme-challenge/ anlegen, welche von der Domain für die man ein Zertifikat beantragt aufrufbar sein muss, also beispielsweise: www.example.com/.well-known/acme-challenge/.

Damit nicht in jedem Domain-Webroot das Verzeichnis angelegt werden muss, wird ein globales Alias in /etc/httpd/conf.d/letsencrypt.conf angelegt:

Alias /.well-known/acme-challenge/ "/srv/www/letsencrypt/.well-known/acme-challenge/"
<Directory "/srv/www/letsencrypt/.well-known/acme-challenge/">
    AllowOverride None
    Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec
</Directory>

Nun noch das Verzeichnis anlegen:

mkdir -pv /srv/www/letsencrypt/.well-known/acme-challenge/
chown -vR letsencrypt:apache /srv/www/letsencrypt/.well-known/
chmod -vR 750 /srv/www/letsencrypt/.well-known/

Dann noch den Webserver neu starten damit die Änderung wirksam wird:

systemctl reload httpd

Erstellen der SSL Verzeichnisse

mkdir -pv /usr/local/share/ssl/letsencrypt/{requests,certs,keys}/
chown -vR letsencrypt:root /usr/local/share/ssl/letsencrypt/
chmod -v 755 /usr/local/share/ssl/letsencrypt/
chmod -v 755 /usr/local/share/ssl/letsencrypt/{requests,certs}
chmod -v 700 /usr/local/share/ssl/letsencrypt/keys

Dazu braucht es noch das folgende Intermediate-Zertifikat von letsenrypt: Let’s Encrypt Authority X3 (IdenTrust cross-signed)
Dieses wird am besten unter: /usr/local/share/ssl/letsencrypt/intermediate.crt abgelegt:

wget 'https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem.txt'
mv -v lets-encrypt-x3-cross-signed.pem.txt /usr/local/share/ssl/letsencrypt/intermediate.crt

Nun braucht der letsencrypt Benutzer noch sudo rechte um den Webserver reloaden zu können:

visudo -f /etc/sudoers.d/letsencrypt
letsencrypt           ALL=(root) NOPASSWD: /etc/init.d/httpd reload, /usr/bin/systemctl reload httpd

Dann wird der Benutzer- und dann ins SSL Verzeichnis gewechselt:

su - letsencrypt
cd /usr/local/share/ssl/letsencrypt/

Für die erstmalige Verwendung muss man einen privaten Schlüssel erstellen mit dem man sich bei letsencrypt anmelden kann, den sogennanten „Account Key“.
Dieser muss nur einmal erstellt- und kann für alle Domains benutzt werden; es ist sozusagen das „Login“ bei letsencrypt:

openssl genrsa 4096 > /usr/local/share/ssl/letsencrypt/account.key
chmod -v 600 /usr/local/share/ssl/letsencrypt/account.key

Nun wird ein Key und CSR für die Domain „example.com“ erstellt.
Da später sowohl „example.com“, wie auch „www.example.com“ aufrufbar sein sollen wird daraus ein sogenanntens SAN-Zertifikat gemacht.

Dazu muss erst eine (sehr rudimentäre) Konfigurationsdatei für openssl erstellt werden:
/usr/local/share/ssl/letsencrypt/openssl.cnf

[ req ]
distinguished_name	= req_distinguished_name
[ req_distinguished_name ]

Dann wird der CSR für das Zertifikat erstellt:

openssl genrsa 4096 > /usr/local/share/ssl/letsencrypt/keys/example.com.key
openssl req -new -sha256 -key /usr/local/share/ssl/letsencrypt/keys/example.com.key -subj "/" -reqexts SAN -config <(cat /usr/local/share/ssl/letsencrypt/openssl.cnf <(printf "[SAN]\nsubjectAltName=DNS:example.com,DNS:www.example.com")) > /usr/local/share/ssl/letsencrypt/requests/example.com.csr

Und die Berechtigungen gesetzt:

chmod -v 600 /usr/local/share/ssl/letsencrypt/keys/example.com.key
chmod -v 640 /usr/local/share/ssl/letsencrypt/requests/example.com.csr

Nun wird das Zertifikat mit acme-tiny von letsencrypt signiert:

/usr/local/bin/acme_tiny.py --account-key /usr/local/share/ssl/letsencrypt/account.key --csr /usr/local/share/ssl/letsencrypt/requests/example.com.csr --acme-dir /srv/www/letsencrypt/.well-known/acme-challenge/ >  /usr/local/share/ssl/letsencrypt/certs/example.com.crt

Und der Webserver reloaded:

sudo /usr/bin/systemctl reload httpd

Damit nun nicht für jede Domain das Script aufgerufen werden muss, wird noch ein Wrapper-Script erstellt, welches das automatisch für jede Datei im CSR Verzeichnis macht:

mkdir -v /srv/www/letsencrypt/bin/
chmod -v 700 /srv/www/letsencrypt/bin/

/srv/www/letsencrypt/bin/renew-certs

#!/bin/bash
ARG_DOMAIN="$1"
DIR_SSL="/usr/local/share/ssl/letsencrypt"
DIR_ACME="/srv/www/letsencrypt/.well-known/acme-challenge"
FILE_ACCOUNTKEY="/usr/local/share/ssl/letsencrypt/account.key"
CMD_ACME="/usr/local/bin/acme_tiny.py"
CMD_WEB_RELOAD="/usr/bin/sudo /etc/init.d/httpd reload"
 
# If a single domain is entered only create/renew the certificate for that domain
if [[ ! -z "$ARG_DOMAIN" ]]; then
  if [[ -f "$DIR_SSL/requests/$ARG_DOMAIN.csr"  ]]; then
    $CMD_ACME --account-key $FILE_ACCOUNTKEY --csr $DIR_SSL/requests/$ARG_DOMAIN.csr --acme-dir $DIR_ACME >  $DIR_SSL/certs/$ARG_DOMAIN.crt.new
    if [ -s $DIR_SSL/certs/$ARG_DOMAIN.crt.new ]
    then
      mv -v $DIR_SSL/certs/$ARG_DOMAIN.crt.new $DIR_SSL/certs/$ARG_DOMAIN.crt
      echo -e "Certificate signed! Check it with:\nopenssl x509 -in $DIR_SSL/certs/$ARG_DOMAIN.crt -text -noout"
    else
      echo "Error creating certificate."
      exit 1
    fi
  else
    echo "A domain specified, but the corresponding CSR does not exist!"
    exit 1
  fi
# Without arguments do it for all domains which have a existing CSR file
else
  for csr in $DIR_SSL/requests/*.csr
  do
    domain=$(basename "${csr%.*}")
    $CMD_ACME --quiet --account-key $FILE_ACCOUNTKEY --csr $DIR_SSL/requests/$domain.csr --acme-dir $DIR_ACME >  $DIR_SSL/certs/$domain.crt.new
    if [ -s $DIR_SSL/certs/$domain.crt.new ]
    then
      mv $DIR_SSL/certs/$domain.crt.new $DIR_SSL/certs/$domain.crt
    fi
  done
fi
$CMD_WEB_RELOAD > /dev/null
chmod -v 700 /srv/www/letsencrypt/bin/renew-certs

Als letztes muss man noch einen cronjob erstellen, welche einmal monatlich die Zertifikate erneuert:

crontab -e
0 0 1 * * /srv/www/letsencrypt/bin/renew-certs
Achtung startssl

Früher benutze man oft startssl.com, welches auch kostenlose Zertifikate bot. Da sich jedoch startssl nicht an die Regeln hielt, vertrauen die Brwoser den Zertifikaten von startssl nicht mehr.
Und da letsencrypt als Inititative der grossen Browserhersteller selber enstanden ist und getragen wird, sollte auf jedenfall letsencrypt für kostenlose Zertifikte genommen werden.

HTTPS testen

Zum Abschluss kann man seine fertig Konfigurierte Domain mit einem SSL Anaylzer, z.B. mit dem ssllabs.com: SSL Server Test überprüfen ob alles ok ist.

Neue Domain hinzufügen

Soll nun eine neue SSL-Domain hinzugefügt werden, reicht es einfach einen Key/CSR zu erstellen und im Webserver zu konfigurieren; der Rest macht das renew-certs Script automatisch.

/etc/httpd/vhosts.d/example.com

<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
DocumentRoot /srv/www/example.com/www/public_html
</VirtualHost>

Achtung: Zuerst darf nur der HTTP VirtualHost erstellt werden, der HTTPS VirtualHost kann erst hinzugefügt werden, wenn das Zertifikat vorhanden ist!

systemctl reload httpd
su - letsencrypt
 
openssl genrsa 4096 > /usr/local/share/ssl/letsencrypt/keys/example.com.key
openssl req -new -sha256 -key /usr/local/share/ssl/letsencrypt/keys/example.com.key -subj "/" -reqexts SAN -config <(cat /usr/local/share/ssl/letsencrypt/openssl.cnf <(printf "[SAN]\nsubjectAltName=DNS:example.com,DNS:www.example.com")) > /usr/local/share/ssl/letsencrypt/requests/example.com.csr
 
chmod -v 600 /usr/local/share/ssl/letsencrypt/keys/example.com.key
chmod -v 640 /usr/local/share/ssl/letsencrypt/requests/example.com.csr
commonName (CN) setzen

Im oben gezeigten Beispiel wird kein „commonName“ gesetzt (-subj „/“).
Für letsencrypt ist das nicht wichtig, es wird dann einfach die erste Domain beim [SAN] Abschnitt als commonName eingesetzt.
Dies kann jedoch suboptimal sein, denn der commonName wird bei jedem Seitenaufruf als „Webseite“ angezeigt. Und so kann es vorkommen, dass die erste Seite nicht „example.com“, sondern „komische.subpage.example.com“ ist, die dann bei jeder Domain im Zertifikat gezeigt wird.

In diesem Fall kann man im CSR den commonName direkt setzten, indem man die Anstelle von -subj "/", beispielsweise folgendes schreibt:
-subj /C=CH/ST=MeinOrt/L=MeinOrt/O=Meine Firma/OU=Meine Abteilung/CN=example.com
bzw. im Script:

openssl req -new -sha256 -key /usr/local/share/ssl/letsencrypt/keys/example.com.key -subj "/C=DE/ST=MeinOrt/L=MeinOrt/O=Meine Firma/OU=Meine Abteilung/CN=example.com" -reqexts SAN -config <(cat /usr/local/share/ssl/letsencrypt/openssl.cnf <(printf "[SAN]\nsubjectAltName=DNS:example.com,DNS:www.example.com")) > /usr/local/share/ssl/letsencrypt/requests/example.com.csr

Hinweis: letsencrypt wird aus dem subject sämtliche Angaben bis auf „CN=“ löschen, es kommt also nicht darauf an, was man bei C=, ST=, L=, O= und OU= angibt.

Der erstellte CSR lässt sich anschliessend Online mit dem CSR Decoder überprüfen.

renew-certs

/etc/httpd/vhosts.d/example.com

<VirtualHost *:443>
ServerName example.com
ServerAlias www.example.com
DocumentRoot /srv/www/example.com/www/public_html
SuexecUserGroup example.com example.com
 
SSLEngine On
SSLCertificateFile        /usr/local/share/ssl/letsencrypt/certs/example.com.crt
SSLCertificateKeyFile     /usr/local/share/ssl/letsencrypt/keys/example.com.key
SSLCertificateChainFile   /usr/local/share/ssl/letsencrypt/intermediate.crt
</VirtualHost>
systemctl reload httpd

Webseite 100% auf SSL Umstellen

Hat man SSL richtig eingerichtet, möchte man die Seite gleich „voll“ auf SSL Umstellen, so dass unverschlüsseltes HTTP gar nicht mehr nötig wäre.
Dazu muss man meist in der Web-Applikation (CMS) das noch entsprechend umstellen, so dass alle links mit https:// erzeugt werden; bei wordpress beispielsweise in den Einstellungen und bei Drupal in der Konfigurationsdatei.

Weiter kann man dann noch in der Datenbank nach http:// links suchen und diese entsprechend ändern. Allerdings ist diese Aktion nicht gerade ungefährlich, als Datenbank vorher sichern!

Um zu überprüfen ob die Seite wirklich 100% SSL ist, hilft der Online-Checker auf whynopadlock.com weiter.

Web-Konfiguration

Nun erstellen wir das „Basis-Verzeichnis“:

mkdir -v /srv/www/
chown -v root:apache /srv/www/
chmod -v 750 /srv/www

/etc/httpd/conf.d/vhosts.conf:

#========================== Global Section ====================================
 
# Include the virtual host configurations:
NameVirtualHost *:80
NameVirtualHost *:443
 
 
#========================== Global directory settings =========================
 
<Directory />
  Order Deny,Allow
  Deny from all
  Options None
  AllowOverride None
</Directory>
 
<Directory "/srv/www">
  AllowOverride All
  Options +SymLinksIfOwnerMatch +Includes
  Order Allow,Deny
  Allow from all
</Directory>
 
 
#========================== Logging settings ==================================
# Global log formats and settings
LogFormat "%V %h %l %u %t \"%r\" %s %b" vcommon
LogFormat "%V %h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" vcombined
 
 
#========================== Default VirtualHosts ==============================
# Here we are catching any request which does not go to a configured VirtualHost
# See: 
# * http://httpd.apache.org/docs/current/vhosts/name-based.html#defaultvhost
# * http://httpd.apache.org/docs/current/vhosts/name-based.html#using
 
#-------------------------- HTTP default --------------------------------------
# Default settings for HTTP
 
<VirtualHost *:80>
  ServerName localhost
  DocumentRoot /srv/www/localhost/www
  CustomLog   /srv/www/localhost/www/log/access.log common
  ErrorLog    /srv/www/localhost/www/log/error.log
</VirtualHost>
 
 
#-------------------------- HTTPS default -------------------------------------
# Default settings for HTTPS
 
<VirtualHost *:443>
  ServerName localhost
  DocumentRoot /srv/www/localhost/www
  ErrorLog    /srv/www/localhost/www/log/ssl_error_log
  TransferLog /srv/www/localhost/www/log/ssl_access_log
  CustomLog   /srv/www/localhost/www/log/ssl_request.log ssl
 
  SSLEngine on
 
  SSLCertificateFile            /etc/pki/tls/certs/localhost.crt
  SSLCertificateKeyFile         /etc/pki/tls/private/localhost.key
  #SSLCertificateChainFile      /etc/pki/tls/certs/server-chain.crt
  #SSLCACertificateFile         /etc/pki/tls/certs/ca-bundle.crt
</VirtualHost>
 
 
#========================== VirtualHosts ======================================
# Include VirtualHosts from directory
Include vhosts.d/*

Nun noch das Web-Verzeichnis für localhost anlegen:

mkdir -pv /srv/www/localhost/www/log
chown -vR apache:apache /srv/www/localhost/
chmod -vR 750 /srv/www/localhost/
Hinweis: Zertifikate

Falls wie oben beschrieben ein „richtiges“ Zertifikat mit letsencrypt erstellt wurde, müssen SSLCertificateFile, SSLCertificateKeyFile und SSLCertificateChainFile entsprechend angepasst werden.

Nun noch für jede Domain ein Virtualhostsfile (jeder Host erhällt auch ein SSL-Virtualhost; damit kann man alle Webseiten auch per SSL abrufen); 0.0.0.0 ist wiederum mit der IP des Servers zu ersetzen:
/etc/httpd/vhosts.d/testdomain.tld

################################################################################
# Virtualhosts File for Domain testdomain.tld
################################################################################
 
<Directory /srv/www/testdomain.tld/www/>
  AllowOverride All
  Options +SymLinksIfOwnerMatch +Includes +ExecCGI
  AddHandler fcgid-script .php
  FCGIWrapper /var/www/fcgi/testdomain.tld/php-fcgi.sh .php
</Directory>
 
<VirtualHost *:80>
ServerName testdomain.tld
ServerAlias www.testdomain.tld
DocumentRoot /srv/www/testdomain.tld/www/public_html
CustomLog /srv/www/testdomain.tld/www/log/combined.log combined
CustomLog /srv/www/testdomain.tld/www/log/access_log common
ErrorLog /srv/www/testdomain.tld/www/log/error_log
SuexecUserGroup testdomain.tld testdomain.tld
</VirtualHost>
 
 
<Directory /srv/www/testdomain.tld/www/>
  AllowOverride All
  Options +SymLinksIfOwnerMatch +Includes +ExecCGI
  AddHandler fcgid-script .php
  FCGIWrapper /var/www/fcgi/testdomain.tld/php-fcgi.sh .php
</Directory>
 
<VirtualHost *:443>
ServerName testdomain.tld
ServerAlias www.testdomain.tld
DocumentRoot /srv/www/testdomain.tld/www/public_html
CustomLog /srv/www/testdomain.tld/www/log/combined.log combined
CustomLog /srv/www/testdomain.tld/www/log/access_log common
ErrorLog /srv/www/testdomain.tld/www/log/error_log
SuexecUserGroup testdomain.tld testdomain.tld
SSLEngine On
SSLCertificateFile      /usr/local/share/ssl/localhost/certs/apache.crt
SSLCertificateKeyFile   /usr/local/share/ssl/localhost/keys/apache.key
#SSLCertificateChainFile /usr/local/share/ssl/localhost/chain.pem
</VirtualHost>
Dynamische VirtualHosts
Bei apache können mittels „VirtualDocumentRoot“ Verzeichnisse mit Laufzeit-Informationen als DocumentRoot dienen.
Vor allem bei massen hosting ist dies sehr praktisch.

Wenn man z.B. einen zentralen log-analyzer installiert hat, der log statistiken generiert, kann man einen Virtuewllen host machen, bei dem stats.example.com automatisch auf /srv/www/shared/stats/public_html/stats/example.com zeigt:

### Public vhost for all domains ###
 
  ServerName stats.hauptserver.tld
  ServerAlias stats.*
  SuexecUserGroup web-shared web-shared
  VirtualDocumentRoot /srv/www/shared/stats/public_html/stats/%2+
 
  Alias /icon/ "/srv/www/shared/stats/public_html/icon/"
 
 
    Options +Indexes

Weitere Verwendung dazu findet man in der apache Dokumentation: Dynamically configured mass virtual hosting und Apache Module mod_vhost_alias.

Hinweis: Wildcard domains
Wenn man im apache sogenannte „wildcard“ domains (*.example.com) macht ist folgendes zu beachten:

  • Wildcard subdomains funktionieren nur in der ServerAlias-Direktive, nicht mit ServerName
  • Ist ein wildcard (*) gesetzt, werden alle nachfolgenden subdomains, auch wenn sie definiert sind ignoriert

Es ist dabei zu beachten, dass man zuerst alle subdomains (sub1.example.com, sub2.example.com, …) setzten muss, bevor man den wildcard (*.example.com) setzt.

Domain / Benutzer hinzufügen

Um nun eine neue Domain, bzw. Benutzer hinzuzufügen muss man folgendes tun:

  • Benutzer erstellen:
adduser domain.tld -G users
usermod -a -G domain.tld apache
  • Verzeichnis und Starterscript erstellen:
mkdir -v /var/www/fcgi/domain.tld /var/lib/php/session/domain.tld
vi /var/www/fcgi/domain.tld/php-fcgi.sh
chown -vR domain.tld:domain.tld /var/www/fcgi/domain.tld /var/lib/php/session/domain.tld
chmod -v 755 /var/www/fcgi/domain.tld/php-fcgi.sh
chmod -v 750 /var/lib/php/session/domain.tld
  • Webverzeichnis erstellen:
mkdir -p /srv/www/domain.tld/www/{log,public_html}
chown -vR domain.tld:domain.tld /srv/www/domain.tld/
chmod -v 550 /srv/www/domain.tld
chmod -v 750 /srv/www/domain.tld/www
chmod -v 570 /srv/www/domain.tld/www/log
chmod -v 750 /srv/www/domain.tld/www/public_html
  • Virtualhost-file erstellen:
vi /etc/httpd/vhosts.d/domain.tld
  • Apache Neu starten:
/etc/init.d/httpd restart

Fehlersuche

Internals Server Error

Wahrscheinlich stimmen die Rechte des Wrappers oder des Verzeichnisses in dem der Wrapper liegt nicht. Prüfen Sie die suEXEC Logdatei (/var/log/httpd/suexec.log) sowie die Apache Error Logdatei (/var/log/httpd/error.log).

mysql Datenbankserver

yum install mysql mysql-server
chkconfig mysqld on
/etc/init.d/mysqld start

Die default Werte von /etc/my.cnf sind hier schon passend.
Möchte man UTF8 verwenden muss man evtl. unter dem Punkt [client] noch folgendes anfügen:

default-character-set  = utf8

und unter: [mysqld]

init-connect="SET NAMES utf8"

FTP-Server

Als letztes installieren wir den FTP-Server:

yum install ftp proftpd
chkconfig proftpd on

Nun noch in: /etc/proftpd.conf für jede Homepage ein Verzeichnis: /srv/www/domain.tld erstellen:

 

Und dein Webserver läuft!

Erweiterungen

Quota

Ob Quota auf filesystem-ebene heute noch Sinn macht, muss jeder für sich entscheiden. In der Regel wird man das Quota ohnehin zusätzlich noch in den Software-Komponenten (ftp-server, mailserver, usw.) konfigurieren.

So installieren wir das File System Quota:

Installieren:

yum install quota

Initialisieren:

Zuerst muss man bei jeder Partition, für die man quota Einsetzen will in der /etc/fstab bei den Options: „usrquota,grpquota“ angefügt werden, z.B.:

defaults,usrquota,grpquota

Danach die Partition remounten, z.B.:

mount -o remount /srv

Nun Initialisert man noch das Quota-File:

quotacheck -cug /srv
quotacheck -avug

Setzen kann man das Quota dann mit:

edquota username

Verwaltung / Administration

Logrotation

Da wir nun keine zentralen logfiles mehr haben müssen wir ein neues logrotate script anlgegen, damit die logs nicht irgendwann das filesysteme zumüllen:

/etc/logrotate.d/httpd-vhosts

/srv/www/*/*/log/*log {
  monthly
  rotate 12
  compress
  delaycompress
  missingok
  notifempty
  sharedscripts
  postrotate
    /sbin/service httpd reload > /dev/null 2>/dev/null || true
  endscript
}

Logfile-Analyzer

Um die Zugriffe auf die Webseite auszuwerten installiert man am besten einen Log-Anlayzer wie AWstats oder goAccess.

Neue Domain erstellen automatisiert

Da es nun ziemlich mühsam wäre bei jeder neuen Domain diese kompletten obigen Schritte durch zu gehen, bietet es sich an ein entsprechend Script zu erstellen, dass dies automatisiert; dieses kann entweder unter /root/bin oder etwa /usr/local/bin liegen.

Dazu können wir gleich das Verzeichnis: /etc/skel/ benutzen; denn sobald ein neuer user im System angelegt wird, wird der Inhalt dieses Verzeichnisses automatisch ins User Verzeichnis kopiert.

Zuerst legt man also die benötigte Verzeichnisstruktur für einen „WebUser“ in diesem Verzeichnis an:

mkdir -pv /etc/skell/www/{log,private,public_html}
touch /etc/skell/.bash_history /etc/skell/.viminfo

Zu beachten ist hier, dass auch die Dateien angelegt werden müssen, die normalerweise beim ersten login automatisch erzeugt würde (z.B. .bash_history), da der WebUser auf der ersten ebene seines HOME-Verzeichnisses keine Schreibberechtigung haben wird.

Weiter werden nun jeweils „template-files“ angelegt um einen VirtualHost zu erzeugen, eine Datenbank aufzusetzen und natürlich der php-fcgi wrapper:

/etc/skel/vhost.template

################################################################################
# Virtualhosts File for Domain domain.tld
################################################################################
 
 
#========================== PROD domain =======================================
 
  ServerName domain.tld
  ServerAlias www.domain.tld
  DocumentRoot /srv/www/domain.tld/www/public_html
  CustomLog /srv/www/domain.tld/www/log/combined.log combined
  CustomLog /srv/www/domain.tld/www/log/access_log common
  ErrorLog /srv/www/domain.tld/www/log/error_log
  SuexecUserGroup domain.tld domain.tld
 
 
    AllowOverride All
    Options +SymLinksIfOwnerMatch +Includes +ExecCGI
    AddHandler fcgid-script .php
    FCGIWrapper /var/www/fcgi/domain.tld/php-fcgi.sh .php
 
 
 
 
#========================== SSL ===============================================
#
#  ServerName domain.tld
#  ServerAlias www.domain.tld
#  DocumentRoot /srv/www/domain.tld/www/public_html
#  CustomLog /srv/www/domain.tld/www/log/combined.log combined
#  CustomLog /srv/www/domain.tld/www/log/access_log common
#  ErrorLog /srv/www/domain.tld/www/log/error_log
#  SuexecUserGroup domain.tld domain.tld
#
#  
#    AllowOverride All
#    Options +SymLinksIfOwnerMatch +Includes +ExecCGI
#    AddHandler fcgid-script .php
#    FCGIWrapper /var/www/fcgi/domain.tld/php-fcgi.sh .php
#  
#

/etc/skel/db.sql.template

CREATE DATABASE domain_tld;
GRANT ALL PRIVILEGES ON `domain_tld`.* TO 'domain.tld'@'%' IDENTIFIED BY 'setpass';
FLUSH PRIVILEGES;

/etc/skel/php-fcgi.sh.template

#!/bin/sh
PHPRC=/srv/www/conf.generic/
export PHPRC
exec /var/www/fcgi/php-fcgi

Nun können wir das domainadd/domaindel Script erstellen; die vorhin erzeugten template files werden von diesem script dann entsprechend weiter verarbeitet:

/root/bin/domainadd

#!/bin/sh
################################################################################
# domainadd / domaindel
# CREATED BY: Steven Varco
#
# Adds or deletes a domain.
# The directory structure of  the domain can be configured in /etc/skel
# For the creation of virtualhosts and DB you can create the follwing templates
# in /etc/skel:
#   - vhost.template
#   - db.sql.template
#   - php-fcgi.sh.template
#   - www/public_html/index.html
# in these files the string "domain.tld" (or "domain_tld" for DB Names) will be
# replaced with the actual domain.
# In the DB template, the string "setpass" will be replaced with a random generated
# password.
#
# HISTORY:
#==============================================================================
# 29.11.2011    Steven Varco    - Created Script
# 22.09.2012    Steven Varco    - added FTP directory creation
# 23.04.2013    Steven Varco    - Changed user group settings
# 25.10.2013    Steven Varco    - changed log-directory (user, permissions)
#                                 for use with log-statisics tools (awstats)
################################################################################
 
 
 
#========================== Configuration =====================================
DOMAIN=$1
DOMAINBASEDIR="/srv/www"
DOMAINDIR="$DOMAINBASEDIR/$DOMAIN"
FTPBASEDIR="/srv/ftp"
SESSION_DIR="/var/local/lib/php/session"
 
PATH_VHOSTS=/etc/httpd/vhosts.d
PATH_FCGI=/var/www/fcgi
GROUPID=100
WEBGROUP="apache"
USERGROUP="users"
LOGGROUP="web-log"
FTPUSERGROUP="ftpusers"
PASSWORD=$(> /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c16)
VERB="-v"
 
 
#========================== Creation ==========================================
if [ -z "$DOMAIN" ]; then
  echo "usage: domainadd "
  exit 1
fi
 
if [ "$DOMAIN" != "" ]
  then
  if [ ! -d $DOMAINDIR  ]
  then
    echo "creating domain: $DOMAIN in: $DOMAINDIR"
 
    # User creation and permissions
    useradd -b $DOMAINBASEDIR -m $DOMAIN -G $USERGROUP
 
    # WWW-Directory creation
    # If /etc/skel is not configured properly, create needed directories
    mkdir -p $VERB $DOMAINDIR/www/{log,private,public_html}
    touch $DOMAINDIR/.bash_history
    chown $VERB $DOMAIN:$DOMAIN $DOMAINDIR/.bash_history
    chmod $VERB 600 $DOMAINDIR/.bash_*
    chown -R $VERB $DOMAIN:$DOMAIN $DOMAINDIR |grep changed
    chgrp -R $VERB $LOGGROUP $DOMAINDIR/*/log |grep changed
    chmod $VERB 551 $DOMAINDIR
    chmod $VERB 551 $DOMAINDIR/www
    chmod $VERB 550 $DOMAINDIR/*/log |grep changed
    find $DOMAINDIR/*/{private,public_html}/ -type d -print0 |xargs -0 chmod $VERB 750 |grep changed
    find $DOMAINDIR/*/{private,public_html}/ -type f -print0 |xargs -0 chmod $VERB 640 |grep changed
    if [ -f "$DOMAINDIR/www/public_html/index.html" ]; then
      perl -p -i -e "~s|domain.tld|$DOMAIN|" $DOMAINDIR/www/public_html/index.html
    fi
 
    # (optional) FTP_directory creation
    mkdir $VERB -p $FTPBASEDIR/$DOMAIN/ftp
    chown -R $VERB $DOMAIN:$FTPUSERGROUP $FTPBASEDIR/$DOMAIN
    chmod $VERB 2550 $FTPBASEDIR/$DOMAIN
    chmod $VERB 2750 $FTPBASEDIR/$DOMAIN/ftp
 
    ### TODO: Add user in mysql table ###
 
    # FCGI stuff
    mkdir $VERB $PATH_FCGI/$DOMAIN
    chown $VERB $DOMAIN:$DOMAIN $PATH_FCGI/$DOMAIN/
    chmod 550 $VERB $DOMAIN:$DOMAIN $PATH_FCGI/$DOMAIN/
    if [ -f "$DOMAINDIR/php-fcgi.sh.template" ]; then
      mv $VERB $DOMAINDIR/php-fcgi.sh.template $PATH_FCGI/$DOMAIN/php-fcgi.sh
      chown $VERB $DOMAIN:$DOMAIN $PATH_FCGI/$DOMAIN/php-fcgi.sh
      chmod 500 $VERB $DOMAIN:$DOMAIN $PATH_FCGI/$DOMAIN/php-fcgi.sh
    fi
    mkdir $VERB $SESSION_DIR/$DOMAIN
    chown $VERB $DOMAIN:$DOMAIN $SESSION_DIR/$DOMAIN
    chmod $VERB 750 $SESSION_DIR/$DOMAIN
 
    # VirtualHost creation
    if [ -f "$DOMAINDIR/vhost.template" ]; then
      perl -p -i -e "~s|domain.tld|$DOMAIN|" $DOMAINDIR/vhost.template
      mv $VERB $DOMAINDIR/vhost.template $PATH_VHOSTS/020-$DOMAIN
      chown $VERB root:root $PATH_VHOSTS/020-$DOMAIN
    fi
 
    # DB creation
    if [ -f "$DOMAINDIR/db.sql.template" ]; then
      DBNAME=${DOMAIN//./_}
      perl -p -i -e "~s|domain.tld|$DOMAIN|" $DOMAINDIR/db.sql.template
      perl -p -i -e "~s|domain_tld|$DBNAME|" $DOMAINDIR/db.sql.template
      perl -p -i -e "~s|setpass|$PASSWORD|" $DOMAINDIR/db.sql.template
    fi
 
    # Activating
    if [ -f "$PATH_VHOSTS/020-$DOMAIN" ]; then
      #/etc/init.d/httpd configtest && /etc/init.d/httpd reload
      echo "to activate changes, execute: /etc/init.d/httpd configtest && /etc/init.d/httpd reload"
    fi
 
    if [ -f "$DOMAINDIR/db.sql.template" ]; then
      echo "To create a corresponding DB, execute: mysql -uroot -p < $DOMAINBASEDIR/$DOMAIN/db.sql.template && rm -v /srv/web/$DOMAIN/db.sql.template"
    fi
  else
    echo "Domain already exists!"
    exit 1
  fi
else
  echo "No domain given!"
  exit 1
fi

/root/bin/domaindel

#!/bin/sh
DOMAIN=$1
DOMAINBASEDIR="/srv/www"
DOMAINDIR="$DOMAINBASEDIR/$DOMAIN"
 
GROUPID=100
WEBGROUP="apache"
USERGROUP="users"
PASSWORD=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c32)
#VERB="-v"
 
if [ -z "$DOMAIN" ]; then
  echo "usage: domainadd example.com"
  exit 1
fi
 
if [ "$DOMAIN" != "" ]
  then
  if [ -d $DOMAINDIR  ]
  then
    rm -f $VERB /etc/httpd/vhosts.d/020-$DOMAIN
    userdel -r $VERB $DOMAIN
    rm -rf $VERB /var/www/fcgi/$DOMAIN
 
    echo "to activate changes, execute: /etc/init.d/httpd configtest && /etc/init.d/httpd reload"
    echo "NOTE: Any user databases and the corresponding DB user have to be removed manually!"
  else
    echo "Domain does not exist!"
    exit 1
  fi
else
  echo "No domain given!"
  exit 1
fi

Nun lässt sich mittels:

domainadd

eine neue Domain erstellen
.

Related Links

Published by

Steven Varco

Steven ist ein Redhat RHCE-Zertifizierter Linux-Crack und ist seit über 18 Jahren sowohl beruflich wie auch privat auf Linux spezialisiert. In seinem Keller steht ein Server Rack mit diversen ESX und Linux Servern.

2 thoughts on “WWW-Server”

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.