File Coverage

blib/lib/CrowdSec/Client.pm
Criterion Covered Total %
statement 21 97 21.6
branch 0 42 0.0
condition 0 12 0.0
subroutine 7 11 63.6
pod 2 4 50.0
total 30 166 18.0


line stmt bran cond sub pod time code
1             package CrowdSec::Client;
2              
3 1     1   85373 use strict;
  1         2  
  1         30  
4 1     1   445 use Date::Parse;
  1         7628  
  1         110  
5 1     1   476 use HTTP::Request::Common;
  1         22841  
  1         67  
6 1     1   685 use JSON;
  1         8800  
  1         4  
7 1     1   845 use LWP::UserAgent;
  1         24763  
  1         32  
8 1     1   568 use Moo;
  1         6860  
  1         5  
9 1     1   1789 use POSIX "strftime";
  1         5368  
  1         9  
10              
11             our $VERSION = '0.04';
12              
13             # Default alert template
14             our %DEFAULTS = (
15             duration => '4h',
16             labels => undef,
17             origin => 'CrowdSec::Client',
18             reason => 'Banned by CrowdSec::Client',
19             scenario => 'Banned by CrowdSec::Client',
20             simulated => JSON::false,
21             type => 'ban',
22             );
23              
24             # Watcher accessors
25             has machineId => ( is => 'ro' );
26             has password => ( is => 'ro' );
27             has token => ( is => 'rw' );
28             has tokenVal => ( is => 'rw' );
29             has strictSsl => ( is => 'ro', default => 1, );
30             has baseUrl => ( is => 'ro' );
31              
32             # Bouncer accessors
33             has apiKey => ( is => 'ro' );
34              
35             # Common accessors
36             has userAgent => (
37             is => 'ro',
38             default => sub {
39             return LWP::UserAgent->new(
40             $_[0]->strictSsl
41             ? ()
42             : (
43             ssl_opts => {
44             verify_hostname => 0,
45             SSL_verify_mode => 0
46             }
47             )
48             );
49             }
50             );
51              
52             has autoLogin => ( is => 'ro' );
53              
54             has error => ( is => 'rw' );
55              
56             sub login {
57 0     0 1   my ($self) = @_;
58 0           my $error = 0;
59 0 0         return 1 if $self->tokenIsvalid;
60 0           foreach my $k (qw(machineId password baseUrl)) {
61 0 0         unless ( $self->{$k} ) {
62 0           $self->error("Missing parameter: $k");
63 0           $error++;
64             }
65             }
66 0 0         return if $error;
67 0           my $request = POST(
68             $self->baseUrl . '/v1/watchers/login',
69             Accept => 'application/json',
70             Content_Type => 'application/json',
71             Content => JSON::to_json(
72             {
73             machine_id => $self->machineId,
74             password => $self->password,
75             scenarios => [],
76             }
77             )
78             );
79 0           my $response = $self->userAgent->request($request);
80 0 0         if ( $response->is_success ) {
81 0           eval {
82 0           my $tmp = JSON::from_json( $response->content );
83 0           $self->token( $tmp->{token} );
84 0           $self->tokenVal( str2time( $tmp->{expire} ) );
85             };
86 0 0         if ($@) {
87 0           $self->error("Bad response content from CrowdSec server: $@");
88 0           return;
89             }
90 0 0 0       if ( !$self->token and !$self->tokenVal ) {
91 0           $self->error(
92             "Missing token and expire fields in CrowdSec response: "
93             . $response->content );
94             }
95 0           return 1;
96             }
97             else {
98 0           $self->error(
99             "Bad response from CrowdSec server: " . $response->status_line );
100 0           return;
101             }
102             }
103              
104             sub banIp {
105 0     0 1   my ( $self, $ip, %params ) = @_;
106 0 0         unless ( $ip ) {
107 0           $self->error('Usage: banIp($ip, \%options)');
108 0           return;
109             }
110 0 0         if ( $self->autoLogin ) {
111 0 0         unless ( $self->login ) {
112 0           return;
113             }
114             }
115 0 0         unless ( $self->token ) {
116 0           $self->error("No valid token");
117 0           return;
118             }
119 0           my %prm = ( %DEFAULTS, %params );
120 0 0         $prm{simulated} = $prm{simulated} ? JSON::true : JSON::false;
121 0           my $stamp = strftime "%Y-%m-%dT%H:%M:%SZ", gmtime(time);
122             my $decision = [
123             {
124             capacity => 0,
125             created_at => $stamp,
126             decisions => [
127             {
128             duration => $prm{duration},
129             origin => $prm{origin},
130             scenario => $prm{scenario},
131             scope => 'Ip',
132             type => $prm{type},
133             value => $ip,
134             }
135             ],
136             events => [],
137             events_count => 1,
138             labels => $prm{labels},
139             leakspeed => '0',
140             message => $prm{reason},
141             scenario => $prm{reason},
142             scenario_hash => '',
143             scenario_version => '',
144             simulated => $prm{simulated},
145 0           source => {
146             ip => $ip,
147             scope => 'Ip',
148             value => $ip,
149             },
150             start_at => $stamp,
151             stop_at => $stamp,
152             }
153             ];
154 0           my $request = POST(
155             $self->baseUrl . '/v1/alerts',
156             Authorization => 'Bearer ' . $self->token,
157             Content_Type => 'application/json',
158             Content => JSON::to_json($decision),
159             );
160 0           my $response = $self->userAgent->request($request);
161 0 0         if ( $response->is_success ) {
162 0           my $response_content = $response->content;
163 0           my $res = eval { JSON::from_json($response_content)->[0] };
  0            
164 0 0         if ($@) {
165 0           $self->error(
166             "CrowdSec didn't return an array: " . $response->content );
167 0           return;
168             }
169 0           return $res;
170             }
171             else {
172 0           print "Échec de la requête : " . $response->status_line . "\n";
173 0           return;
174             }
175             }
176              
177             sub testIp {
178 0     0 0   my ( $self, $ip ) = @_;
179 0           my $error = 0;;
180 0           foreach my $k (qw(apiKey baseUrl)) {
181 0 0         unless ( $self->{$k} ) {
182 0           $self->error("Missing parameter: $k");
183 0           $error++;
184             }
185             }
186 0 0         unless ($ip) {
187 0           $self->error('Missing IP');
188 0           $error++;
189             }
190 0 0         return (0, $self->error) if $error;
191 0           my $response = $self->userAgent->get(
192             $self->baseUrl . "/v1/decisions?ip=$ip",
193             Accept => 'application/json',
194             'X-Api-Key' => $self->apiKey,
195             );
196 0 0         if ( $response->is_success ) {
197 0           my @decisions;
198 0 0 0       return 0 if !$response->content or $response->content eq 'null';
199 0           eval {
200 0           my $tmp = JSON::from_json( $response->content );
201 0           @decisions = @$tmp;
202             };
203 0 0         if ($@) {
204 0           $self->error("Bad response content from CrowdSec server: $@");
205 0           return (0, $self->error);
206             }
207 0 0         return 0 unless @decisions;
208 0           foreach my $decision (@decisions) {
209 0 0 0       if ( $decision->{type} and $decision->{type} eq 'ban' ) {
210 0           return 1;
211             }
212             }
213 0           return 0;
214             };
215             }
216              
217             sub tokenIsvalid {
218 0     0 0   my ($self) = @_;
219 0   0       return ( $self->tokenVal and ( $self->tokenVal > time ) );
220             }
221              
222             1;
223             __END__