Blog

Start 1 2 3 4 5

Auf dem Weg zu Perl 5.36 - builtin

20.05.2022 // Renée Bäcker

Mit Perl 5.36 gibt es ein neues Pragma: builtin. Damit lassen sich neue Hilfsfunktionen in das Skript/Modul importieren. Derzeit bietet das Pragma folgende Hilfsfunktionen:

  • true, false, isbool
  • weaken, unweaken, isweak
  • blessed
  • refaddr, reftype
  • ceil, floor
  • trim

Booleans

use v5.35;
 
use builtin qw(true false is_bool);
no warnings 'experimental::builtin';
 
my $true = true;
my $false = false;
 
say is_bool( $true );
say is_bool( $false );

Aktuell ist das Pragma als experimentell eingestuft. Beim Einbinden des Pragmas muss angegeben werden, welche Funktionen importiert werden sollen (alternativ kann man bei den Aufrufen einfach den vollqualifizierten Namen angeben, z.B. builtin::true().

true und false erstellt jeweils Werte, die im boolschen Kontext wahr bzw. unwahr liefern. In Strings und/oder Berechnungen werden aber die Standardwerte für wahr/*unwahr* genommen: 1 bzw. "" (Leerstring).

Es gibt einen Unterschied zu den Werten, die man bisher typischerweise für die boolschen Werte verwendet hat: In den Interna der Variablen wird nun zur Laufzeit gespeichert, dass es sich um boolsche Werte handelt:

use Devel::Peek;

my $true = builtin::true;
my $false = builtin::false;

Dump( $true ); Dump( $false );

Liefert

SV = PVNV(0x5653938f9380) at 0x56539391f968
  [...]
  PV = 0x565392196bc4 "1" [BOOL PL_Yes]
  [...]
SV = PVNV(0x5653938f93a0) at 0x56539391f938
  [...]
  PV = 0x565392196bc3 "" [BOOL PL_No]
  [...]

Man sieht hier den Zusatz [BOOL PL_{Yes|No}]. Diese Zusatzinformation wird auch von builtin::is_bool ausgewertet. Damit kann man unterscheiden, ob die 1 ein boolscher Wert ist oder einfach die Zahl 1:

my $zahl = 1;
my $wahr = builtin::true;

print sprintf '$zahl ist bool: %s, $wahr ist bool: %s', 
    builtin::is_bool( $zahl ),
    builtin::is_bool( $wahr );

mit der Ausgabe

$zahl ist bool: , $wahr ist bool: 1

Referenzen

Auf Referenzen arbeiten einige Hilfsfunktionen:

  • weaken, unweaken, isweak
  • blessed
  • refaddr, reftype

Diese Funktionen sind auch in Scalar::Util verfügbar, deshalb zeige ich hier nur die – in meinen Augen – wichtigsten Funktionen weaken, blessed und reftype.

Mit blessed kann man herausfinden, ob eine Variable eine ge*blessed*e Referenz (sprich ein Objekt) ist. Ist das der Fall, wird der entsprechende Paketname zurückgeliefert:

use builtin qw(blessed);
no warnings 'experimental::builtin';

use Math::BigInt;
 
my $int = Math::BigInt->new(2022);
 
if ( blessed $int ) {
    say '$int is blessed';
    say "package: ", blessed $int;
}

liefert

$ perl blessed.pl 
$int is blessed
package: Math::BigInt

Mit reftype bekommt man den Namen der Datenstruktur, auf die die Referenz verweist. Die meisten werden die Funktion ref kennen:

my $array_ref = [];
say ref( $array_ref ); # liefert ARRAY

Soweit so gut. Sobald es aber ein Objekt ist, wird nicht der Name der Datenstruktur ausgegeben, sondern der Name des Pakets:

my $array_object = bless [], 'Array';
say ref( $array_object ); # liefert Array, also den Paketnamen

reftype liefert in jedem Fall den Namen der Datenstruktur:

my $array_object = bless [], 'Array';
say reftype( $array_object ); # liefert ARRAY

Werden komplexe Datenstrukturen aufgebaut, kann es schnell passieren, dass man zirkuläre Referenzen aufbaut:

my %hash_eins;
my %hash_zwei = ( eins => \%hash_eins );
$hash_eins{zwei} = \%hash_zwei;

Damit hat man ein Speicherleck erzeugt. Perl nutzt einen Referenzzähler, um Speicher freizugeben. Und der kann hier nie auf 0 gehen, da in einer zirkulären Referenz immer A auf B verweist und B auf A.

In so einem Fall kann man weaken nutzen. Damit wird der Referenzzähler der Variable nicht hochgezählt:

my %hash_eins;
my %hash_zwei = ( eins => \%hash_eins );

$hash_eins{zwei} = \%hash_zwei;
builtin::weaken( $hash_eins{zwei} );

Zu diesem Problem werden wir noch einen weiteren Blogartikel schreiben.

Endlich trimmen

Eine kleine Funktionalität, die aber auch bei den Entwicklern von Perl viel Diskussionen hervorgerufen hat. Es gibt auf CPAN etliche Implementierungen davon. Leerzeichen am Anfang und am Ende eines Strings entfernen.

Warum gab es diese Diskussionen? Es ging hauptsächlich darum, was das trimmen genau machen soll. Einfach nur Leerzeichen oder noch andere Whitespaces? Soll der Nutzer die Möglichkeit haben, die zu entfernenden Zeichen selbst festzulegen? Soll das ganze Inplace (wie s///) passieren oder soll der veränderte Wert zurückgegeben werden?

Mit trim werden die Whitespaces am Anfang und am Ende eines Strings entfernt und der veränderte Wert wird zurückgegeben:

use builtin qw(blessed);
no warnings 'experimental::builtin';

my $t = "\t   \ntest    \n    "; 
say ">>",$t,"<<"; 

my $trimmed = builtin::trim($t); s
ay ">>$trimmed<<"

liefert die Ausgabe

>>     
test    
    <<
>>test<<

Im Laufe der Zeit werden sicher noch etliche nützliche Hilfsfunktionen im builtin::-Namensraum landen. Schon in Perl 5.36 sind viele Helferlein enthalten, die schon so oft vermisst wurden. Die Vorfreude auf das neue Release wächst.



Permalink:

Auf dem Weg zu Perl 5.36 - try/catch/finally

12.05.2022 // Renée Bäcker

Es ist nicht schön, wenn man eine Anwendung hat, die vielleicht mittendrin einfach aufhört zu laufen. Vielleicht ist die Anwendung in einen Fehler gelaufen und vielleicht gibt es keine ordentliche Fehlermeldung. Woran hat es gelegen? An welcher Stelle ist der Fehler aufgetreten?

In Perl nutzt man häufig eval {} , um Fehler abzufangen und eine ordentliche Fehlerbehandlung zu machen. Das hat aber auch Schwächen, so steckt der Fehler in der Spezialvariablen $@ und je nach Situation könnte diese Variable vor der Fehlerbehandlung wieder geleert werden.

Auf CPAN gibt es mehrere Module, die das try/catch-Konstrukt aus anderen Programmiersprachen als Perl-Modul umsetzen. Seit Perl 5.34 gibt es dieses Werkzeug direkt mit dem Perl-Kern und mit Perl 5.36 noch ein weitere Verbesserung. Das ist allerdings noch als experimentell gekennzeichnet.

Schauen wir uns den Standard-Fall mal an:

eval {
    die "Help!";
};

if ( $@ ) {
    warn "An error occured: $@";
}

In Zeile 2 wird ein die ausgeführt, was zu einem Programmabbruch führen würde. Dank des Block-eval wird dieser Abbruch aber abgefangen und es kann eine ordentliche Fehlerbehandlung durchgeführt werden.

In das try/catch überführt, sieht das so aus:

use feature 'try';
no warnings 'experimental::try';
 
try {
    die 'This died';
}
catch ( $error ) {
    warn "The code inside 'try' died with error: '$error'";
}

Der try-Block sieht genauso aus wie beim eval. Allerdings schließt sich hier direkt ein catch-Block an, dem der Fehler aus dem try-Block übergeben wird. Dieser Fehler landet in $error.

Wird bei dem die ein Fehlerobjekt übergeben, landet genau dieses Objekt auch in $error:

use feature 'try';
no warnings 'experimental::try';
 
package ErrorObject { use Moo; has message => ( is => 'ro' ) }
 
try {
    die ErrorObject->new( message => 'An error occured' );
}
catch ( $error ) {
    warn sprintf "The code inside 'try' died with error: '%s'",
        $error->message;
}

In Perl 5.36 kommt die Möglichkeit hinzu, einen finally-Block zu definieren. Dieser wird auf jeden Fall ausgeführt – egal, ob ein Fehler aufgetreten ist oder nicht. Außerdem ist es egal, ob in dem catch-Block ein exit aufgerufen wird oder nicht.

Dieser Block kann daher gut für abschließende Schritte verwendet werden, die auf jeden Fall ausgeführt werden sollen:

use feature 'try';
no warnings 'experimental::try';
 
package ErrorObject { use Moo; has message => ( is => 'ro' ) }
 
try {
    die ErrorObject->new( message => 'An error occured' );
}
catch ( $error ) {
    warn sprintf "The code inside 'try' died with error: '%s'",
        $error->message;
    exit;
}
finally {
    print "clean up...";
}





Permalink:

Auf dem Weg zu Perl 5.36 – Änderungen mit "use v5.36"

06.05.2022 // Renée Bäcker

Mittels use v5.<version> können Features und Standardeinstellungen für die angegebene Perl-Version geladen werden. Und es ist die minimal notwendige Perl-Version. Ein use v5.10 verlangt, dass das Programm mindestens mit Perl 5.10 ausgeführt wird. Darüber hinaus wird zum Beispiel das Feature say aktiviert.

Mit use v5.36 ändern sich einige Dinge, die in den folgenden Absätzen beschrieben werden.

warnings wird aktiviert

Es ist seit vielen Jahren bewährte Praxis, dass zu den ersten Zeilen eines jeden Programms oder Moduls die Zeilen use strict; und use warnings; gehören sollen. Mit use v5.36 ist das nicht mehr notwendig, denn damit wird beides automatisch aktiviert (strict wird schon seit längerem auf diese Weise aktiviert).

use v5.35;
 
my $s = 'test';
$s += 1;

Gibt diese Warnung aus:

Argument "test" isn't numeric in addition (+)

Einige Konstrukte werden zu echten Fehlern

Es gibt Dinge, die in Perl möglich sind aber schon seit vielen Jahren nicht mehr empfohlen werden. Drei dieser Dinge werden jetzt zu echten Fehlern. Ohne das use v5.36 laufen die noch, aber von der Verwendung rate ich ab.

Wer auch mit use v5.36 diese Konstrukte verwenden möchte, muss das explizit über use feature "feature_name" tun.

Multidimensional Hashes

Ich muss gestehen, dass ich diese Konstrukt noch nie in produktivem Code gesehen geschweige denn verwendet habe.

my %x;

$x{1,'talk'} = 'Perl Features';
print $x{1,'talk'};

Wie man sieht, ist hier der Hash kein einzelner Wert sondern eine Liste an Werten. Mit use v5.36 ist das nicht mehr erlaubt und die Fehlermeldung

Multidimensional hash lookup is disabled

erscheint.

Für diejenigen, die es interessiert: Bei diesen multidimensionalen Hashes werden die Werte der Liste zu einem Schlüssel zusammengefügt, so dass es ein normaler Hash ist. Die Werte werden dabei mit \x1C (File Separator) konkateniert, so dass man theoretisch auf den oben gezeigten Hasheintrag auch über

print $x{"1\x1Ctalk"};

zugreifen kann.

Indirekte Methodenaufrufe

Diese Schreibweise dürfte Programmierern mit Erfahrung in anderen Programmiersprachen bekannt vorkommen:

my $object = new Klasse;

Eigentlich sollte

my $object = Klasse->new;

verwendet werden.

Die erste Schreibweise nennt man indirekte Methodenaufrufe. Der Code kann so funktionieren wie es gewünscht ist, muss aber nicht. Das Problem wird vielleicht mit etwas Code deutlicher:

use v5.10;

package Klasse {
  sub new  { return bless {}, shift }
  sub test { return "test" }
};

sub hallo { return "hallo" };

my $object = new Klasse;
say $object;

say hallo $object;

my $test = test $object;
say $test;

say test $object;

Was passiert hier ab der Zeile 11?

In den Zeilen davor wird nur etwas Vorbereitung betrieben. Zeile 1 wird benötigt, damit das say zur Verfügung steht. Und in den Zeilen 3 bis 6 wird ein package definiert, das die zwei Subs new und test bereitstellt. Hier ist das package eine Klasse. In Zeile 8 wird noch eine Subroutine hallo definiert, die zum Hauptprogramm gehört.

Schon Gedanken dazu gemacht, was in den restlichen Zeilen des Codes passiert? Die Ausgabe sieht so aus:

Klasse=HASH(0x55e4b7c81340)
hallo
test
say() on unopened filehandle test at ...

In Zeile 10 wird die Funktion new des Pakets Klasse aufgerufen. Diese gibt ein Objekt zurück. Die Zeile 1 der Ausgabe bestätigt das. Funktioniert.

In Zeile 13 wird die Funktion hallo des Hauptprogramms ausgeführt und diese bekommt das Objekt als ersten Parameter übergeben (das sieht man jetzt nicht an der Ausgabe, kann aber durch eine kleine Änderung am Code selbst überprüft werden). Funktioniert.

In Zeile 15 wird die Methode test des Objekts aufgerufen, das Ergebnis (test) landet in der Variable $test und diese wird in Zeile 16 ausgegeben. Funktioniert.

Zeile 18 provoziert einen Fehler. Offensichtlich denkt perl, dass test ein Dateihandle ist (siehe auch nächstes Konstrukt), und nicht die Methode test des Objekts. Das heißt, ich muss bei dieser Schreibweise aufpassen, in welchem Umfeld ich die Methode aufrufen will.

Und was passiert wenn in Zeile 9 noch eine Subroutine test (sub test { "test2"} ) eingefügt wird? In diesem Fall wird perl diese Funktion in den Zeilen 15 und 18 genau diese Funktion ausführen und die Ausgabe wäre

Klasse=HASH(0x55e4b7c81340)
hallo
test2
test2

Es gibt noch mehr Fallen. Aus diesem Grund sind diese indirekten Methodenaufrufe mit use v5.36 nicht mehr erlaubt. Allerdings kann perl nur die indirekten Methodenaufrufe wie in Zeile 10 erkennen und es kommt zu der Fehlermeldung

Bareword "Klasse" not allowed while "strict subs" in use

Die Probleme oben verdeutlichen aber, warum grundsätzlich die Schreibweise mit -> verwendet werden soll. Denn damit weiß der Interpreter immer, welche Funktion/Methode ausgeführt werden muss.

Bareword Dateihandles

In vielen Perl-Tutorials, die so im Internet zu finden sind, wird das Schreiben einer Datei mit folgendem Code (oder so ähnlich) gezeigt:

open FH, ">/tmp/datei.txt" or die $!;
print FH "Eine Zeile\n";
close FH;

Dieses FH ist ein sogenanntes Bareword Filehandle. Der Name kann frei gewählt werden. Für einen Einstieg in Perl oder in Einzeilern ist die Verwendung dieser Bareword Filehandles auch ganz gut. Aber wer etwas Erfahrung mit Perl gesammelt hat, sollte darauf verzichten und zu lexikalischen Dateihandles übergehen:

open my $fh, ">", "/tmp/datei.txt" or die $!;
print $fh "Eine Zeile\n";
close $fh;

Ein Vorteil dieser lexikalischen Dateihandles haben wir beim vorherigen Konstrukt gezeigt. Was wenn es zufällig eine Subroutine mit dem Namen des *Bareword*s gibt? Es gibt aber noch weitere Vorteile: Die lexikalischen Dateihandles werden automatisch geschlossen, wenn sie den Gültigkeitsbereich verlassen (auch wenn man schon allein wegen der Fehlerbehandlung selbst das close machen sollte). Und sie sind eben lexikalisch, also nicht global.

Die Bareword Filehandles sind global und können so zu Seiteneffekten führen. Ein Beispiel:

use warnings;

read_data();

sub read_data {
    open FH, '</tmp/data.txt' or die $!;
    while ( my $line = <FH> ) {
        print $line;
        add_to_log( $line ) if $line =~ m{error};
    }
    close FH;
}

sub add_to_log {
    open FH, '>/tmp/log.txt' or die $!;
    print FH $_[0];
    close FH;
}

Wenn /tmp/data.txt eine Zeile mit error enthält, sieht die Ausgabe so aus:

ein
error
readline() on closed filehandle FH at ...

Das globale Filehandle wurde also in einer Subroutine geschlossen, bevor alle Zeilen der Datei in der Ursprungssubroutine gelesen werden konnten.

Benutzt man die Bareword Filehandles mit use v5.36, gibt es eine entsprechende Fehlermeldung:

Bareword filehandle "FH" not allowed under 'no feature "bareword_filehandles"' at ...

Einige Standard-*Bareword Filehandles* wie STDIN, STDOUT, STDERR und DATA werden auch mit use v5.36 funktionieren.


Permalink:

Auf dem Weg zu Perl 5.36 – Signaturen

28.04.2022 // Renée Bäcker

Nicht einmal 1 Monat, dann wird es die nächste Version von Perl 5 geben: Perl 5.36. Um die Wartezeit etwas zu verkürzen, werden wir hier ein paar Neuerungen vorstellen. Der erste Teil hat dann aber etwas zum Thema, das gar nicht mehr so neu ist: (Methoden-)Signaturen. Schon seit Perl 5.20 (also rund acht Jahre) gibt es die Signaturen. Aber jetzt endlich sind sie nicht mehr als experimentell gekennzeichnet.

Mit den Signaturen kann der Code übersichtlicher gestaltet werden und man automatisch eine Überprüfung der Anzahl der übergebenen Parameter dabei.

Der einfachste Fall sind verpflichtende Parameter:

use v5.35; # aktiviert auch strict und warnings

crawl( 'https://perl-academy.de' );

sub crawl ($url) {
    say "URL: $url";
    
    # hole die Seite und extrahiere die Links
    # siehe Beispielcode im Git-Repository
}

Hier hat die Subroutine crawl genau einen Pflichtparameter – $url. Die Variable muss nicht extra deklariert werden und diese ist nur in der Subroutine gültig. In der Subroutine selbst kann ganz normal mit der Variablen gearbeitet werden.

In diesem Beispiel ersetzt das

sub crawl ($url) {
}

mehr oder weniger nur ein

sub crawl {
    my ($url) = @_;
}

Sieht erst einmal nach nicht viel aus. Mit der Verwendung der Signaturen gibt es aber eine Prüfung der Anzahl an übergebenen Parameter. Der Aufruf crawl() erzeugt dann die Fehlermeldung

Too few arguments for subroutine 'main::crawl' (got 0; expected 1)

und der Aufruf crawl('https://perl-academy.de', 1) erzeugt die Fehlermeldung

Too many arguments for subroutine 'main::crawl' (got 2; expected 1)

Möchte man etwas flexibler in den Aufrufen sein, muss also etwas an den Signaturen geändert werden. Eine Möglichkeit ist, slurpy Parameter zu nutzen. Soll crawl beliebig viele Parameter akzeptieren, kann man

sub crawl ($url, @more_parameters) {
}

nutzen. Damit landet der erste übergebene Parameter in $url und alle weiteren Parameter in @more_parameters. Es kann auch ein Hash benutzt werden:

sub crawl ($url, %opts) {
}

Damit landet bei einem Aufruf mit crawl('https://perl-academy.de', max_redirects => 2) die URL in $url und im Hash %opts gibt es den Schlüssel max_redirects mit dem Wert 2.

Aber diese beiden Möglichkeiten sind nicht immer die beste Wahl, weil dann unterschiedliche Parameter in einem Array landen. Deshalb gibt es auch die Möglichkeit, mittels default-Werten die Parameter optional zu machen und sie mit einem vorgegebenen Wert zu belegen:

sub crawl ($url, $max_redirects = 2) {
}

Bei einem Aufruf mit crawl('https://perl-academy.de', 5) ist der Wert von $max_redirects natürlich 5, bei einem Aufruf mit crawl('https://perl-academy.de' ) ist der Wert der Variablen 2.

Der default-Wert muss nicht nur eine Zahl oder ein String sein, sondern kann ein Subroutinen-Aufruf sein, vorherige Parameter können verwendet werden und auch ein do{}-Block ist möglich:

sub crawl ($url, $max_redirects = get_config('max_redirects') ) {
}

sub crawl ($url, $api_url = $url . '/api/v1' ) {
}

sub crawl ( $url, $max_redirects = do { warn "Using default value"; 2; } ) {
}

Es gibt noch die Möglichkeit, unbenannte Variablen zu nutzen, wenn man an der Verwendung von Werten nicht interessiert ist. Die Verwendung dieser unbenannten Variablen ist unter https://metacpan.org/release/SHAY/perl-5.35.11/view/pod/perlsub.pod#Signatures nachzulesen. Das würde diesen Blogartikel zu sehr ausdehnen und diese Variablen spielten bisher bei in meinem Code keine Rolle.

Was Signaturen nicht bietet, sind Prüfungen von Datentypen. Es wird also nicht geprüft, ob der übergebene Skalar eine Arrayreferenz, Hashreferenz oder einfach ein String ist. Das muss weiterhin selbst gemacht werden. Auch benannte Parameter (beispielsweise crawl( url => 'https://perl-academy.de') ) sind derzeit nicht möglich.

Aber schon die gezeigten Möglichkeiten können das Programmieren stark vereinfachen.

Der Beispielcode ist im Git-Repository unter https://os.perl-services.de/perl-academy/blog-codesamples/-/tree/main/2022/04/5_36_signaturen zu finden.



Permalink:

Bericht Deutscher Perl/Raku-Workshop 2022

06.04.2022 // Renée Bäcker

Ende März fand in der 24. Deutsche Perl/Raku-Workshop statt. Nachdem im letzten Jahr die Veranstaltung auf Grund der Corona-Pandemie nur online stattfand, war in diesem Jahr ein Teil der Teilnehmer in Leipzig anwesend. Weitere Teilnehmer lauschten den Vorträgen im Live-Stream. Corona bzw. die Covid-19-Zertifikate waren Thema bei domm, der gezeigt hat, wie er mit einer Perl-Anwendung die QR-Codes der Zertifikate validiert und geprüft hat.

Generell waren einige Talks eher auf die Zukunft ausgerichtet: So berichtete Curtis Ovid Poe über den aktuellen Stand von Corinna, einer neuen Objektorientierung, die hoffentlich bald in den Perl-Core einfließt. Paul Evans, der an Corinna mitentwickelt und mit Object::Pad ein Testfeld für die Features hat, berichtete von seinen Plänen in On the Road to 2025. Nicht ganz soweit – nur bis zum Release von Perl 5.36 im Mai/Juni diesen Jahres – habe ich vorausgeschaut mit einem Überblick über die neuen Features.

Während der Lightning Talks hat Geizhals angekündigt, dass sie Paul Evans und damit die Corinna-Entwicklung mit einem nicht ganz kleinen Geldbetrag unterstützen werden.

930b8aa3-57d1-4dde-ae7c-cae03639804b.png

Bild: Die Teilnehmer vor Ort, Quelle: Max Maischein

Auch weniger technische Vorträge gab es. Sören Sörries ging in seinem verschiedenen Vorträgen auf verschiedene Lernangebote und -techniken ein.

Raku-Vorträge gab es (zu) wenige: Geizhals' Anwesenheitsmonitor Werda wurde mit Raku und Cro umgesetzt. Maroš Kollár hat gezeigt, wie die Anwendung umgesetzt wurde.

Ansonsten waren die Vorträge bunt gemischt: Von der Chart-Erstellung mit Perl, über Neuigkeiten zu YAML bis zu den Kollegen von denen man gar nicht weiß, dass man sie hat (gemeint sind z.B. die Autoren der Perl-Module die man einsetzt).

Die Videos der einzelnen Vorträge werden in wenigen Tagen online sein. Wir werden darüber auch twittern.

Und im nächsten Jahr geht es dann nach Frankfurt. Ein genaues Datum steht noch nicht fest, aber die Webseite ist schon online und Vorträge können bereits eingereicht werden. Wer bisher noch keine Erfahrungen mit dem Halten von Vorträge gesammelt hat, kann auch erst einmal einen Lightning Talk einreichen. Max Maischein hat es so schön gesagt: Es sind nur 5 Minuten. Und wenn etwas schiefgeht – man weiß es sind nur 5 Minuten. Und auch ich habe 2004 mit einem Lightning Talk angefangen...

Ich möchte mich bei den lokalen Orgas von Leipzig/Halle.pm und bei Max bedanken. Ihr habt einen tollen Workshop auf die Beine gestellt!


Permalink:

perlbrew

14.07.2021 // Gregor Goldbach

Das System-Perl zu verwenden hat viele Nachteile. Diese können behoben werden, wenn man ein eigenes Perl in seinem Benutzerverzeichnis installiert. Mit dem Werkzeug perlbrew kannst du mehrere Perl-Installationen nebeneinander auf einem System konfliktfrei betreiben.

Die Probleme mit dem System-Perl

Die Anwendungsentwicklung mit dem System-Perl zu koppeln bringt viele Nachteile mit sich. Hier einige Beispiele:

  • Perl kann nicht unabhängig vom System aktualisiert werden, wenn du Probleme beheben möchtest, neue Sprach-Features nutzen willst – oder vielleicht sogar ganz bewusst ein Perl nutzen möchtest, das älter als das System-Perl ist.
  • In Organisationen sind Entwickler häufig von den Systemadministratoren abhängig, die die Stabilität des gesamten Systems und nicht nur einer Anwendung beachten müssen, und deswegen nicht »einfach so« Perl aktualisieren können.
  • Die Installation von CPAN-Modulen ist wegen fehlender Berechtigungen nicht möglich.
  • Der Test von Anwendung unter neueren Perl-Versionen auf einem System ist nicht einfach möglich.

Eine Lösung ist perlbrew, das die Installation mehrerer Perl-Versionen im Benutzerverzeichnis ermöglicht, so dass keine Administratorrechte benötigt werden. Das Werkezeug isoliert diese Installationen voneinander und ermöglicht einen einfachen Wechsel zwischen ihnen.

Admin-freie Installation von Perl-Versionen

Die Installation von perlbrew ist auf zwei Wegen möglich. Die einfachste geht über cpanm:

$ cpanm App::perlbrew
$ perlbrew init

Solltest du keine Module von CPAN installieren können (siehe meine Anmerkung oben zu fehlenden Adminstratorrechten), kannst du dir ein Installationsskript herunterladen und es von der Shell ausführen lassen. Aus Sicherheitsgründen verweise ich dich dafür auf die Homepage.

perlbrew kann alle offiziell verfügbaren Perl-Versionen bauen und installieren. Welche das sind, kannst du dir auflisten lassen:

$ perlbrew available
   perl-5.33.7   
   perl-5.32.1   
   perl-5.30.3   
   perl-5.28.3   
  perl-5.26.3.tar.bz2
  ...
    perl-5.8.9.tar.bz2   
    perl-5.6.2   
  perl5.005_03   
  perl5.004_05   
  cperl-5.29.2   
  cperl-5.30.0   
  cperl-5.30.0-RC1 

Aus diesen Versionen wählst du dir eine aus. Diese wird dann in das Verzeichnis perl5/perlbrew im Benutzerverzeichnis installiert:

perlbrew install perl-5.33.7
$ perlbrew install perl-5.33.7
Fetching perl 5.33.7 as $HOME/perl5/perlbrew/dists/perl-5.33.7.tar.gz
Download http://www.cpan.org/src/5.0/perl-5.33.7.tar.gz to $HOME/perl5/perlbrew/dists/perl-5.33.7.tar.gz
Installing /Users/glauschwuffel/perl5/perlbrew/build/perl-5.33.7/perl-5.33.7 into ~/perl5/perlbrew/perls/perl-5.33.7

This could take a while. You can run the following command on another shell to track the status:

  tail -f ~/perl5/perlbrew/build.perl-5.33.7.log

Wenn du den in der letzten Zeile angegeben Befehl in einer zweiten Shell ausführt, kannst du dem Installationprozess »zusehen«.

Welches Perl die Shell ausführt, wird durch den Ausführungspfad bestimmt. Durch die Änderung dieses Ausführungspfads wird zwischen den installierten Perl-Versionen umgeschaltet. Dafür gibt es den perlbrew-Befehl use:

$ perlbrew use perl-5.33.7

Durch mehrfache Verwendung von perlbrew use kannst du in einer Arbeitssitzung in der Shell zwischen den Versionen wechseln. Beim Öffnen einer weiteren Shell wird aber weiterhin das System-Perl verwendet. Wenn du dauerhaft eine Perl-Version verwenden möchtest, kann du mit dem Befehl switch die gewünschte Version in der Shell-Konfiguration ablegen:

perlbrew switch perl-5.33.7

Gelegentlich kommt es bei der Arbeit mit perlbrew vor, dass kurzfristig das System-Perl benutzt werden soll:

perlbrew switch-off

Mit den hier gezeigten Befehlen hast du nun die grundlegenden Werkzeuge zusammen, um dir die Perl-Versionen zu installieren, die du benötigst. Da sich alles in deinem Benutzerverzeichnis abspielt, hast du die volle Kontrolle.

Zusammenfassung

In diesem Artikel habe ich einen kurzen Überblick über die Möglichkeiten gegeben, die perlbrew bietet. Dabei konnte ich vieles nur anreißen. Wenn du jetzt neugierig geworden bist und tiefer einsteigen möchtest, empfehle ich dir die Homepage des Projekts, um mehr über weitere Befehle zu erfahren.


Permalink:

Bericht vom Deutschen Perl-/Raku-Workshop 2021

23.06.2021 // Renée Bäcker

Der Deutsche Perl-/Raku-Workshop (German Perl Workshop, GPW) ist eine Institution in der Perl-Community: Seit 1999 gibt es diese Konferenz rund um Perl (und seit ein paar Jahren Raku). Damit ist der GPW eine der ältesten Perl-Veranstaltungen überhaupt.

In diesem Jahr fand die Konferenz zum ersten Mal rein online statt. Wir waren als Sponsor und mit einigen Vorträgen auf der Konferenz vertreten. Die Vorträge waren auf drei Nachmittage vom 24.-26. März 2021 verteilt.

Über die Technik hinter dem Workshop wollen die Organisatoren noch ein Artikel auf perl.com veröffentlichen. Ein paar (für mich) besondere Vorträge will ich hier ganz kurz beleuchten.

Nach der allgemeinen Begrüßung gab es einen Talk von Paul Evans über Perl in 2025, in dem er (mögliche) neue Features in Perl und den Weg dahin vorgestellt hat. Ein try {} catch {} hat es ja mittlerweile in den Perl-Core geschafft (wird demnächst mit Perl 5.34 als experimentelles Feature ausgeliefert) und weitere Features probiert Paul über CPAN-Module aus. Es lohnt sich, seinem CPAN-Account zu folgen und das Video zeigt viele interessante Ideen.

Ein etwas älteres Programm stellte Sec vor: Beopardy, das jährlich auf dem CCC-Kongress beim Hacker Jeopardy läuft und für die Jeopardy-typische Fragewand und das Handling der Buzzer zuständig ist. In dem Vortrag wird die interessante Entwicklung von Beopardy gezeigt... Meine letzten Erfahrungen mit Perl/Tk liegen schon einige Jahre zurück und ich habe so einige Probleme damit selbst erlebt.

Auch Raku war ein Thema auf der Konferenz. Es wurde z.B. das Pheix CMS vorgestellt. Ein CMS in Raku klingt gut, aber warum man Daten eines CMS in einer Blockchain speichern muss, weiß ich nicht.

Für mich weit relevanter war da der Vortrag von Jonathan Worthington über Things you may not know about Cro. Ich nutze Cro derzeit, um Informationen über unsere Domains, Cloudserver etc. zu sammeln und konnte bei dem Vortrag einiges mitnehmen. Cro ist eine Sammlung von Bibliotheken zur Entwicklung von verteilten Systemen und bietet z.B. eine Klasse für HTTP-Server, Websockets und HTTP-Clients.

Da ich selbst schon ein paar Entwicklerversionen von Perl veröffentlicht habe, gab es beim Vortrag Perl-Release für jedermann von Max Maischein nichts neues für mich. Wer wissen möchte, was hinter einem Release von Perl steckt, sollte sich aber das Video dazu anschauen.

Die Videos der Talks sind unter https://media.ccc.de/c/gpw2021/GPW2021 erreichbar.

Eine Online-Konferenz ist bei weitem nicht das gleiche wie eine Präsenzveranstaltung – gerade wenn man sonst viele Freunde treffen würde. Aber der diesjährige GPW hat gezeigt, dass es auch in diesen Zeiten interessante Dinge gibt, über die gesprochen werden sollte.

Und auch die soziale Komponente blieb nicht ganz aus: Am zweiten Abend gab es noch ein gemeinsames Kochen. Die Rezepte dazu sind online zu finden.

Auch dass die Vorträge nur nachmittags waren, fand ich gut. Zum einen ist es schwer, sich den ganzen Tag auf Videos zu konzentrieren und zum anderen konnte man den Vormittag noch anderweitig nutzen.

Wir hoffen, dass es im nächsten Jahr wieder vor Ort klappt. Angekündigt ist der 24. Deutsche Perl-/Raku-Workshop für den 30. März bis 01. April 2022 in Leipzig.


Permalink:

Kommandozeilenwerkzeuge mit App::Cmd

15.02.2021 // Gregor Goldbach

Das Schreiben von CLI-Tools erfordert einiges an Infrastrukturcode, um ein komfortables Tool mit Kommandos zu erstellen. Teile dieses Codes gleichen sich bei der Implementierung von Unterkommandos. Die Distribution App::Cmd hilft mit Mitteln der objektorientierten Entwicklung dabei, ein komfortables CLI-Tool schnell und erweiterbar zu implementieren.

Infrastrukturcode als lästige Notwendigkeit

Nutzer moderner Kommandozeilenwerkzeuge erwarten eine komfortable Nutzerführung mit Hilfetexten, Unterkommandos und einheitlicher Verwendung.

Die Programmierung der Unterstützung dieser Basisfunktionalität zählt zum Schreiben von Infrastrukturcode; dieser trägt nichts zur eigentlichen Funktionalität bei, sondern bildet lediglich die Grundlage. Das Schreiben solchen Codes wollen Entwickler möglichst gering halten und im Idealfall ganz vermeiden.

Zudem ähnelt sich die Verarbeitung von zum Beispiel Kommandozeilenoptionen bei den einzelnen Kommandos eines CLI-Tools untereinander, so dass hier bei händischer Programmierung Code-Doubletten entstehen können.

In diesem Artikel zeige ich den Einstieg in App::Cmd. Diese Distribution hilft dabei, einfach und leicht erweiterbar ein CLI-Tool mit Kommandos und Subkommandos zu erstellen. Das Schreiben von Infrastrukturcode wird dabei stark verringert.

Ein einfaches Beispiel

Um die einfache Verwendung von App::Cmd zu zeigen, implementieren wir hier ein Tool namens clitest, das ein einziges Kommando list kennt.

Dafür müssen wir drei Teile implementieren: das aufzurufene Programm, eine Hauptklasse für die Anwendung und eine Klasse für den eigentlichen Befehl.

Das Programm

Wir bauen das Werkzeug als Perl-Distribution auf. Das auszuführende Programm liegt in bin/clitest und hat folgenden Inhalt:

#!/usr/bin/perl
use CLITest;
CLITest->run;

Das Programm lädt die Hauptklasse der Anwendung und führt eine Methode aus, die sich dann um die Verarbeitung der Kommandozeilenargumente kümmert.

Die Anwendungsklasse

Die Hauptklasse der Anwendung ist ähnlich schlank wie das eben gezeigte Programm:

package CLITest;
use App::Cmd::Setup -app;
1;

Hier wird der Namensraum CLITest festgelegt, in dem sich alle Befehle als Packages befinden.

Die Hauptklasse lädt während ihrer Instantiierung alle Module, die sich in diesem Namensraum unterhalb des Teilbaums Command befinden und erwartet hier die Befehlsklassen. Die Klasse für unseren Befehl list implementieren wir nun.

Die Befehlsklasse

Wir haben eine Befehlsklasse CLITest::Command::list:

# ABSTRACT: list files
package CLITest::Command;
use CLITest -command;
use File::Find::Rule;

sub usage_desc { "list <dir>" }

sub description { "The list command lists ..." }

sub execute {
  my ($self, $opt, $args) = @_;
 
    my $path=$args->[0];

    my $rule = File::Find::Rule->new;
    $rule->file;
    $rule->name('*');
    my @files = $rule->in( $path );
    print "$_\n" in @files;
}

1;

In ihr definieren wir die Methode execute. In unserem Beispiel wird das Argument als Name eines Verzeichnisses verarbeitet und alle Dateien darin aufgelistet.

Jetzt haben wir schon ein lauffähiges CLI-Tool und können es aufrufen:

perl -Ilib bin/clitest list

Das war es auch schon.

Die nächsten Schritte

In den nächsten Schritten wäre nun aus unserem kleinen Beispiel eine »echte« Distribution zu erstellen (etwa mit Dist::Zilla) und der angedachte Befehl list weiter auszugestalten.

App::Cmd bringt ein Tutorial mit, in dem der Autor beschreibt, wie der Entwickler …

  • Hilfetexte für Befehle festgelegen kann,
  • Optionen und Unterkommandos beschreiben kann,
  • und wie über eine Konfigurationsdatei Vorgabewerte für Optionen festgelegt werden können.

Zusammenfassung

In diesem Artikel habe ich einen Einstieg in App::Cmd gezeigt. Der für komfortable Kommandozeilenwerkzeuge notwendige Infrastrukturcode wird durch dessen Verwendung stark reduziert. Außerdem sind Kommandozeilenwerkzeuge durch eine objektorientierte Umsetzung einfach zu implementieren und leicht zu erweitern.


Permalink:

An Modul-Autoren: Badges für CPANCover

09.02.2021 // Renée Bäcker

Sowohl für Nutzer als auch für Entwickler ist es ganz schön, wenn auf einen Blick der Zustand eines Moduls ersichtlich ist: Sind Fehler bekannt? Kann das Projekt gebaut werden? Wie ist die Testabdeckung?

Dazu können z.B. bei Gitlab oder GitHub sogenannte Badges angezeigt werden. Hier ein kleines Beispiel unseres Moduls Markdown::Table.

74d1bd7f-4df4-4983-8579-298bdacef60a.png

Paul Johnson stellt eine Seite bereit, auf der die Testabdeckung mit Devel::Cover angezeigt wird: CPANCover. Für das oben genannte Modul ist die Testabdeckung unter http://cpancover.com/latest//Markdown-Table-0.04/index.html zu finden.

Wir stellen einen Dienst bereit, der die Daten von CPANCover als einen solchen Badge zur Verfügung stellt: CPANCoverBadge. Das ist eine ganz kleine Anwendung, die auch auf CPAN liegt. Es werden einfach nur die Daten von CPANCover ausgelesen und mit Badge::Simple ein solches Badge erstellt. Für Markdown::Table 0.04 ist das Badge dann unter https://cpancoverbadge.perl-services.de/Markdown-Table-0.04 zu finden.

Um dieses Badge z.B. in der README.md anzuzeigen, muss mit Markdown-Mitteln die Grafik angezeigt werden:

[![CPAN Cover Status](https://cpancoverbadge.perl-services.de/Markdown-Table-0.04)](https://cpancoverbadge.perl-services.de/Markdown-Table-0.04)

Für alle, die Dist::Zilla nutzen, gibt es ein Plugin, um diverse Badges einzubauen: Dist::Zilla::Plugin::GitHubREADME::Badge

Damit muss in der dist.ini ein Block eingefügt werden, in der die Badges konfiguriert werden:

[GitHubREADME::Badge]
badges = travis
badges = cpants
badges = issues
badges = cpancover
phase = build
place = top

Damit werden die vier oben gezeigten Badges eingeblendet.


Permalink:

CPAN-News Januar 2021

06.02.2021 // Renée Bäcker

Der Januar ist rum, Zeit mal nachzuschauen was wir im Januar so alles auf CPAN geladen haben.

Zurerst ein Blick auf komplett neue Module:

Dist-Zilla-Plugin-SyncCPANfile

Wir nutzen *cpanfile*s um die Abhängigkeiten in unseren Projekten zu definieren. Und wir nutzen Dist::Zilla zum Bauen von Distributionen. Dafür gibt es das Plugin Dist::Zilla::Plugin::CPANFile. Das erstellt das cpanfile aber erst in der Distribution. Wir möchten die Datei aber direkt im Repository haben.

Aus diesem Grund haben wir Dist::Zilla::Plugin::SyncCPANfile geschrieben, das die in der dist.ini gelisteten Abhängigkeiten in ein cpanfile schreibt.

Folgendes einfach in die dist.ini schreiben (aus der SYNOPSIS):

# in dist.ini
[SyncCPANfile]
 
# configure it yourself
[SyncCPANfile]
filename = my-cpanfile
comment  = This is my cpanfile

OTRS-OPM-Validate

Ein weiteres Modul, das neu geschrieben wurde, ist OTRS::OPM::Validate. Mit diesem Modul ist es möglich, Erweiterungen für Ticketsysteme wie Znuny, OTOBO, ((OTRS)) Community Edition validieren. Die Dateien sind XML-Dateien mit bestimmten Inhalten.

Da die Entwicklung aber nicht mit einem bestimmten Schema erfolgte und XML::LibXML::Schema nicht das neueste XML-Schema unterstützt, musste was eigenes her.

Das Modul nutzt Reguläre Ausdrücke sehr intensiv...

Aber auch bestehende Module haben ein Update erfahren:

DNS::Hetzner

Dieses Modul ermöglicht es, das REST-API für DNS von Hetzner zu nutzen. Im Januar wurde das Modul mehr oder weniger komplett neu geschrieben, es hat etliche Tests bekommen und die Klassen für das API wurden neu generiert, so dass auch die neuesten Endpunkte unterstützt werden.

use DNS::Hetzner;
use Data::Printer;
 
my $dns = DNS::Hetzner->new(
    token => 'ABCDEFG1234567',    # your api token
);
 
my $records = $dns->records;
my $zones   = $dns->zones;
 
my $all_records = $records->list;
p $all_records;

CPANfile::Parse::PPI

Da dieses Modul ein statischer Parser für *cpanfile*s ist, wurde ein strict-Modus eingeführt. Damit bricht das Modul ab, wenn dynamische Konstrukte wie

    for my  (qw/
        IO::All
        Zydeco::Lite::App
    /) {
        requires , '0';
    }

in der Datei stehen.

use CPANfile::Parse::PPI -strict;
use Data::Printer;
 
my $required = do { local $/; <DATA> };
my $cpanfile = CPANfile::Parse::PPI->new( \$required );
 
my $modules = $cpanfile->modules;
 
__DATA__
for my $module (qw/
    IO::All
    Zydeco::Lite::App
/) {
    requires $module, '0';
}

OTRS-OPM-Installer

Hier wurden nur ein paar Änderungen vorgenommen, weil OTRS::OPM::Parser auf Moo umgestellt wurde und sich das Verhalten ein wenig geändert hat (einige Attribute liefern keine Arrays mehr, sondern Arrayreferenzen).

OTRS-OPM-Parser

Jetzt wird zum Validieren der .opm-Dateien unser neues Module OTRS::OPM::Validate genutzt.


Permalink:

Sicherheit für Perl-Anwendungen: fail2ban

03.02.2021 // Renée Bäcker

Ist eine Webanwendung öffentlich erreichbar, wird es nicht lange dauern und irgendwelche Bots versuchen sich anzumelden. Oder es werden wild irgendwelche URLs aufgerufen. Auch wenn die Anmeldeversuche wahrscheinlich scheitern, geht die Bot-Aktivität zu Lasten der Webanwendung. Und mit genügend versuchen klappt es vielleicht doch mal, dass sich jemand Unbefugtes anmeldet.

Aus diesem Grund sollte man IP-Adressen nicht dauerhaft auf die Webanwendung lassen. Sie sollten nach einer bestimmten Anzahl an Fehlversuchen ausgesperrt werden.

Unter Linux kann man das bequem mit iptables machen. Aber wie ist die Syntax um jemanden auszusperren? Und muss man die Fehlversuche selbst raussuchen und dann die IPs sperren? Nein. Für diese Aufgabe gibt es fail2ban.

fail2ban ist ein Tool, das die Firewallregeln von iptables aktualisiert. Also IP-Adressen sperren und die Sperre wieder aufheben kann. Nach der Installation mittels

apt install fail2ban

kann das Tool direkt loslegen und z.B. SSH-Loginversuche auswerten. Dazu nimmt es die SSH-Logs und durchsucht diese nach Fehlversuchen. Wird eine bestimmte Anzahl von Fehlversuchen registriert, werden die Regeln von iptables aktualisiert.

Damit das Tool nicht nur SSH-Logs durchsucht und Angriffsversuche entdeckt, werden sogenannte Filter für verschieden Anwendungen wie Apache, MySQL, Nagios und viele mehr zur Verfügung gestellt.

Man kann aber auch eigene Anwendungen schützen. Als Beispiel nehmen wir eine Mini-Anwendung, die mit Mojolicious umgesetzt ist:

#!/usr/bin/perl

use strict;
use warnings;

use Mojolicious::Lite -signatures;

get '/' => 'index';
post '/' => sub ($c) {
    if ( $c->param('user') ne 'Test' ) {
        return $c->redirect_to('/');
    }

    $c->session( logged_in => 1 );
    $c->redirect_to( '/welcome' );
};

under '/' => sub ($c) {
    if ( !$c->session('logged_in') ) {
        $c->redirect_to('/');
        return;
    }

    return 1;
};

get '/welcome' => sub ($c) {
    $c->render( data => 'Welcome!' );
};

app->start;

__DATA__
@@ index.html.ep
<form action="/" method="post">
<input type="text" name="user">
</form>


Es gibt eine Route, um sich anzumelden und ist die Anmeldung erfolgreich bekommt man eine Willkommensseite. Nichts spezielles.

Um diese Anwendung mit fail2ban zu schützen, müssen wir etwas mehr loggen - nämlich die erfolglosen Loginversuche.

# [...]
    if ( $c->param('user') ne 'Test' ) {
        $c->app->log->warn(
            sprintf "Login failed for host <%s>",
                $c->tx->remote_address
        );
        return $c->redirect_to('/');
    }
# [...]

Wenn wir jetzt die Anwendung aufrufen und ganz häufig die falschen Zugangsdaten eingeben oder nicht-existente Routen aufrufen, passiert nichts. Die Anwendung kann immer wieder aufgerufen werden.

Jetzt richten wir fail2ban ein.

Als erstes benötigen wir einen Filter. Dazu erstellen wir die Datei /etc/fail2ban/filter.d/app.conf mit folgendem Inhalt:

[Definition]
failregex = .* Login failed for host "<HOST>"
ignoreregex =

Bei failregex wird ein Regulärer Ausdruck angegeben, mit dem fail2ban einen Fehlversuch im Logfile erkennt. Das <HOST> ist ein spezieller Platzhalter, mit dem fail2ban IP-Adressen erkennt. Alternativ könnte man auch (?:::f{4,6}:)?(?P<host>\\S+) schreiben.

Diesen Filter können wir direkt testen

$ fail2ban-regex ~/app/log/development.log /etc/fail2ban/filter.d/app.conf 

Running tests
=============

Use   failregex filter file : app, basedir: /etc/fail2ban
Use         log file : /home/dev/app/log/development.log
Use         encoding : UTF-8


Results
=======

Failregex: 3 total
|-  #) [# of hits] regular expression
|   1) [3] .* Login failed from host "<HOST>"
`-

Ignoreregex: 0 total

Date template hits:
|- [# of hits] date format
|  [65] Year(?P<_sep>[-/.])Month(?P=_sep)Day 24hour:Minute:Second(?:,Microseconds)?
`-

Lines: 65 lines, 0 ignored, 3 matched, 62 missed [processed in 0.00 sec] 
Missed line(s): too many to print.  Use --print-all-missed to print all 62 lines

Da wir schon falsche Zugangsdaten eingegeben haben, sollte der Test ein paar Treffer finden.

Da der Filter noch nicht aktiv ist, schauen wir uns mal an, wie die iptables-Regeln aktuell aussehen:

$ sudo iptables -L
[sudo] Passwort für otrsvm: 
Chain INPUT (policy ACCEPT)
target     prot opt source               destination         
f2b-sshd   tcp  --  anywhere             anywhere             multiport dports ssh

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         

Chain f2b-sshd (1 references)
target     prot opt source               destination         
RETURN     all  --  anywhere             anywhere

Man sieht bei den Regeln für den eingehenden Datenverkert das Ziel f2b-sshd. Und weiter unten genauer aufgeführt was dieses Ziel bedeutet.

Wir müssen fail2ban jetzt so konfigurieren, dass es einen sogenannten Jail für unsere Anwendung gibt. Dazu fügen wir in der Datei /etc/fail2ban/jail.conf folgendes hinzu:

[app-name]
enabled  = true
port     = 3000
filter   = app
logpath  = /path/to/app/log/development.log
action   = iptables[name=AppName, port=3000, protocol=tcp]
maxretry = 5
bantime  = 60

Folgende Angaben werden hier gemacht:

  • enabled = true heißt, dass dieses Jail aktiv ist
  • filter = app bedeutet, dass der Filter app.conf genutzt wird
  • logpath gibt den Pfad zu Logdatei an, die gefiltert werden soll
  • Bei action gibt man an, was beim Erreichen der maximalen Funde gemacht werden soll. Hier wird ein iptables-Eintrag gemacht
  • Unter maxretry gibt man die Anzahl der maximalen Funde an. Hier darf man maximal 5 Fehlversuche machen.
  • bantime gibt die Zeit (in Sekunden) an, die eine IP-Adresse gebannt wird.

Jetzt versuchen wir uns wieder mehrfach anzumelden. Geben wir jetzt 5 mal die falschen Zugangsdaten, ist die Anwendung nicht mehr erreichbar:

dc319a34-5203-46dd-8eb6-cb076787cac9.png

Im fail2ban.log ist jetzt folgendes zu finden:

2020-10-28 12:38:11,757 fail2ban.jail           [472]: INFO    Creating new jail 'app'
2020-10-28 12:38:11,757 fail2ban.jail           [472]: INFO    Jail 'app' uses pyinotify
2020-10-28 12:38:11,758 fail2ban.filter         [472]: INFO    Set jail log file encoding to UTF-8
2020-10-28 12:38:11,763 fail2ban.jail           [472]: INFO    Initiated 'pyinotify' backend
2020-10-28 12:38:11,818 fail2ban.actions        [472]: INFO    Set banTime = 60
2020-10-28 12:38:11,826 fail2ban.filter         [472]: INFO    Set maxRetry = 5
2020-10-28 12:38:11,829 fail2ban.filter         [472]: INFO    Set jail log file encoding to UTF-8
2020-10-28 12:38:11,894 fail2ban.filter         [472]: INFO    Added logfile = /home/dev/app/log/development.log
2020-10-28 12:38:12,173 fail2ban.filter         [472]: INFO    Set findtime = 600
2020-10-28 12:38:12,192 fail2ban.jail           [472]: INFO    Jail 'sshd' started
2020-10-28 12:38:12,203 fail2ban.jail           [472]: INFO    Jail 'app' started
2020-10-28 12:39:07,383 fail2ban.filter         [472]: INFO    [app] Ignore 127.0.0.1 by ip
2020-10-28 12:41:02,691 fail2ban.filter         [472]: INFO    [app] Found 192.168.123.1
2020-10-28 12:41:04,382 fail2ban.filter         [472]: INFO    [app] Found 192.168.123.1
2020-10-28 12:41:06,460 fail2ban.filter         [472]: INFO    [app] Found 192.168.123.1
2020-10-28 12:41:07,980 fail2ban.filter         [472]: INFO    [app] Found 192.168.123.1
2020-10-28 12:41:10,028 fail2ban.filter         [472]: INFO    [app] Found 192.168.123.1
2020-10-28 12:41:10,447 fail2ban.actions        [472]: NOTICE  [app] Ban 192.168.123.1

Wenn wir jetzt in die iptables-Regeln schauen, sehen wir eine Veränderung:

~$ sudo iptables -L
Chain INPUT (policy ACCEPT)
target     prot opt source               destination         
f2b-AppName  tcp  --  anywhere             anywhere             tcp dpt:3000
f2b-sshd   tcp  --  anywhere             anywhere             multiport dports ssh

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         

Chain f2b-AppName (1 references)
target     prot opt source               destination         
REJECT     all  --  192.168.123.1  anywhere             reject-with icmp-port-unreachable
RETURN     all  --  anywhere             anywhere            

Chain f2b-sshd (1 references)
target     prot opt source               destination         
RETURN     all  --  anywhere             anywhere 

Beim Ziel f2b-AppName gibt es für die aufrufende IP jetzt einen REJECT-Eintrag. Durch die bantime=60-Angabe im Jail, wird der Eintrag nach 1 Minute aber wieder gelöscht.

Die Anwendung und der Filter sind auch im Gitlab-Repository zu finden.


Permalink:

Start 1 2 3 4 5