Security
Webserver
21.08.2019 |
Andreas Müller
Vor einigen Jahren bin ich von Apache zu NginX umgestiegen, da ich meine Webseiten in Docker-Container verpackt hatte und der WebServer zum Proxy für den jeweiligen Container wurde.
Hier hat NginX einfach die Nase vorne und glänzt durch eine deutlich einfachere Konfiguration. Zudem lassen sich auch andere Streams durchleiten und nebenbei mittels SSL Zertifikat absichern (z.B. ein TCP-Stream).
Basic-Auth für NginX
Unter Apache ist die Absicherung von Verzeichnissen mittels Basic-Auth sehr einfach zu realisieren, wenn eine .htaccess-Datei verwendet wird. Eine andere Art habe ich damals nie benutzt.
Diese Authentifizierung ist auch unter NginX relativ einfach möglich, benötigt jedoch die Installation der apache2-utils
.
Installation htpasswd und erzeugen einer htpasswd Datei:
# apt-get install apache2-utils
# htpasswd /etc/nginx/auth/htpasswd $user
Mit dieser Datei kann nun die Einrichtung stattfinden: z.B. /etc/nginx/sites-enabled/default
server {
# ... sonstige Einrichtung des Server-Bereichs
location /data {
# ... Einrichtung des Ordners
# Hier wird Basic-Auth aktiviert (und das Realm gesetzt)
auth_basic "Restricted Area";
# Hier wird angegeben, wo die Benutzer und Passwörter gespeichert sind
auth_basic_user_file /etc/nginx/auth/htpasswd;
location /public {
# Auf diesen Ordner dürfen wieder alle zugreifen
auth_basic off;
}
}
}
Mehr Flexibilität mit Lua
Zur Erweiterung der Serverlogik mit komplexeren Abfragen steht in NginX ein Lua-Modul zur Verfügung. Dies muss vermutlich noch installiert werden:
# apt-get install libnginx-mod-http-lua lua-nginx-string
Im Anschluss kann die Konfiguration so geändert werden, dass ein Lua-Script die Basic-Auth Prüfung übernimmt.
server {
# ...
location /restricted {
# Hier wird das Realm gesetzt
set $lua_realm 'Restricted Area';
# Wir definieren noch eine bestimmte Gruppe für die spätere Abfrage
set $lua_group 'restricted group';
# Hier wird die Authentifizierung an das Lua-Script übergeben
access_by_lua_file /etc/nginx/auth/auth.lua;
# ... restliche Einrichtung
}
}
Wer nun der Sprache Lua mächtig ist, kann hergehen und mittels Lua eine vollständige Authentifizierung auf Basis von Basic-Auth vornehmen.
Ich für meinen Teil wollte gerne einen Benutzer-Abgleich über MySQL realisieren. Doch da ich Lua jetzt nicht so wirklich beherrsche, habe ich recherchiert, wie man andere Scripte (z.B. Bash oder eben PHP) ausführen kann.
So wird nun in Lua nun die Basic-Auth Implementierung vorgenommen und der Abgleich mit der Datenbank erledigt ein PHP-Script im Hintergrund.
/etc/nginx/auth/auth.lua:
-- Basic-Auth with lua
function authenticate()
-- does the auth header exist?
local header = ngx.req.get_headers()['Authorization']
if header == nil or header:find(' ') == nil then
return false
end
-- check whether its a Basic-Auth
local divider = header:find(' ')
if header:sub(0, divider-1) ~= 'Basic' then
return false
end
-- try to decode the Basic-Auth
local auth = ngx.decode_base64(header:sub(divider+1))
if auth == nil or auth:find(':') == nil then
return false
end
-- parse the auth information
divider = auth:find(':')
local username = auth:sub(0, divider-1)
local password = auth:sub(divider+1)
-- now my lua knowledge is done...
-- so request something to validate the information
local handle = io.popen(string.format('php /etc/nginx/auth/auth.php %s %s %s', ngx.var.lua_group, username, password))
local response = handle:read("*a")
handle:close()
-- check the script response
if response == nil or response ~= 'OK' then
return false
end
return true
end
local isValid = authenticate()
if not isValid then
ngx.header.content_type = 'text/plain'
ngx.header.www_authenticate = string.format('Basic realm="%s"', ngx.var.lua_realm)
ngx.status = ngx.HTTP_UNAUTHORIZED
ngx.say(string.format('ERROR 401: Unauthorized\nArea: %s', ngx.var.lua_realm))
end
/etc/nginx/auth/auth.php
<?php
// The MySQL database information
$db_host = 'localhost';
$db_port = 3306;
$db_name = 'nginx-auth';
$db_user = 'auth-user';
$db_pass = 'some-password';
$group = '';
$user = '';
$password = '';
if (!empty($argv[1])) {
$group = trim($argv[1]);
}
if (!empty($argv[2])) {
$user = trim($argv[2]);
}
if (!empty($argv[3])) {
$password = trim($argv[3]);
}
try {
$db = new PDO('mysql:host='.$db_host.';port='.$db_port.';dbname='.$db_name.';', $db_user, $db_pass);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTIION);
$query = 'SELECT 1 AS OK FROM `nginx-auth-table` WHERE `is_active` = 1 AND `group` = :group AND `username` = :user AND `password` = SHA2(:pass, 256);';
$stmt = $db->prepare($query);
$stmt->bindValue(':group', $group);
$stmt->bindValue(':user', $user);
$stmt->bindValue(':pass', $password);
$stmt->execute();
$res = $stmt->fetchAll(PDO::FETCH_OBJ);
if (count($res) == 1) {
echo 'OK';
exit(0);
}
}
catch (Exception $ex) {
echo 'ERROR: '.$ex->getMessage();
exit(1);
}
echo 'ERROR: Invalid credentials';
exit(1);
?>
Ein kurzes Fazit
Ich persönlich bin immer wieder sehr erstaunt darüber, wie groß die Palette an Optionen ist, mit der man NginX erweitern kann.
Dabei muss man sich zwar manchmal ein paar Tricks überlegen, doch letzten Endes gibt es immer eine Lösung.
Die Idee über Lua ein beliebiges anderes Script auszuführen eröffnet viele neue Möglichkeiten, wie man die Authentifizierung lösen könnte (PHP war lediglich meine Wahl).