source: repository/lib/Metabrik/Client/Elasticsearch/Query.pm

Last change on this file was 901:246044148483, checked in by GomoR <gomor@…>, 6 weeks ago
  • bugfix: client::elasticsearch::query: use precision_threshold to 100% when counting unique values in unique_term Command
  • new: client::imap: reset_current, read_next_with_an_attachment and save_attachments Commands
  • new: client::smbclient: download_in_background Command
  • bugfix: client::smbclient: download Command to better parse Arguments to easily download files by specifying a full remote Windows path
  • new: email::message: save_attachments Command
  • new: file::compress: handle bzip2 compress/uncompress
  • new: forensic::pcap: show_sessions Command
  • new: forensic::sysmon: use_regex_match Attribute (disables by default)
  • new: forensic::sysmon: clean_ps_from_list Command to accept an optional Argument to only compare from specified sources
  • bugfix: format::latex: copy style file in current directory before compiling Latex
  • update: remote::sandbox: no more imap stuff here.
  • new: remote::sandbox: use_regex_match Attribute, diff_ps_target_filename_created, loop_and_download_created_files and memdump_as_volatility Commands
  • bugfix: xorg::xrandr: added set_output_resolution alias
File size: 15.7 KB
Line 
1#
2# $Id$
3#
4# client::elasticsearch::query Brik
5#
6package Metabrik::Client::Elasticsearch::Query;
7use strict;
8use warnings;
9
10use base qw(Metabrik::Client::Elasticsearch);
11
12sub brik_properties {
13   return {
14      revision => '$Revision$',
15      tags => [ qw(unstable) ],
16      author => 'GomoR <GomoR[at]metabrik.org>',
17      license => 'http://opensource.org/licenses/BSD-3-Clause',
18      attributes => {
19         nodes => [ qw(node_list) ], # Inherited
20         index => [ qw(index) ],     # Inherited
21         type => [ qw(type) ],       # Inherited
22         from => [ qw(number) ],     # Inherited
23         size => [ qw(count) ],      # Inherited
24         client => [ qw(INTERNAL) ],
25      },
26      attributes_default => {
27         index => '*',
28         type => '*',
29      },
30      commands => {
31         create_client => [ ],
32         reset_client => [ ],
33         query => [ qw(query index|OPTIONAL type|OPTIONAL) ],
34         get_query_result_total => [ qw($query_result|OPTIONAL) ],
35         get_query_result_hits => [ qw($query_result|OPTIONAL) ],
36         get_query_result_timed_out => [ qw($query_result|OPTIONAL) ],
37         get_query_result_took => [ qw($query_result|OPTIONAL) ],
38         term => [ qw(kv index|OPTIONAL type|OPTIONAL) ],
39         unique_term => [ qw(unique kv index|OPTIONAL type|OPTIONAL) ],
40         unique_values => [ qw(field index|OPTIONAL type|OPTIONAL) ],
41         wildcard => [ qw(kv index|OPTIONAL type|OPTIONAL) ],
42         range => [ qw(kv_from kv_to index|OPTIONAL type|OPTIONAL) ],
43         top => [ qw(kv_count index|OPTIONAL type|OPTIONAL) ],
44         top_match => [ qw(kv_count kv_match index|OPTIONAL type|OPTIONAL) ],
45         match => [ qw(kv index|OPTIONAL type|OPTIONAL) ],
46         match_phrase => [ qw(kv index|OPTIONAL type|OPTIONAL) ],
47         from_json_file => [ qw(json_file index|OPTIONAL type|OPTIONAL) ],
48         from_dump_file => [ qw(dump_file index|OPTIONAL type|OPTIONAL) ],
49      },
50      require_modules => {
51         'Metabrik::File::Json' => [ ],
52         'Metabrik::File::Dump' => [ ],
53      },
54   };
55}
56
57sub create_client {
58   my $self = shift;
59
60   my $ce = $self->client;
61   if (! defined($ce)) {
62      $ce = $self->open or return;
63      $self->client($ce);
64   }
65
66   return $ce;
67}
68
69sub reset_client {
70   my $self = shift;
71
72   my $ce = $self->client;
73   if (defined($ce)) {
74      $self->client(undef);
75   }
76
77   return 1;
78}
79
80sub get_query_result_total {
81   my $self = shift;
82   my ($query_result) = @_;
83
84   my $run = $self->context->do('$RUN');
85   $query_result ||= $run;
86   $self->brik_help_run_undef_arg('get_query_result_total', $query_result) or return;
87   $self->brik_help_run_invalid_arg('get_query_result_total', $query_result, 'HASH') or return;
88
89   if (! exists($query_result->{hits})) {
90      return $self->log->error("get_query_result_total: invalid query result, no hits found");
91   }
92   if (! exists($query_result->{hits}{total})) {
93      return $self->log->error("get_query_result_total: invalid query result, no total found");
94   }
95
96   return $query_result->{hits}{total};
97}
98
99sub get_query_result_hits {
100   my $self = shift;
101   my ($query_result) = @_;
102
103   my $run = $self->context->do('$RUN');
104   $query_result ||= $run;
105   $self->brik_help_run_undef_arg('get_query_result_hits', $query_result) or return;
106   $self->brik_help_run_invalid_arg('get_query_result_hits', $query_result, 'HASH') or return;
107
108   if (! exists($query_result->{hits})) {
109      return $self->log->error("get_query_result_hits: invalid query result, no hits found");
110   }
111   if (! exists($query_result->{hits}{hits})) {
112      return $self->log->error("get_query_result_hits: invalid query result, no hits in hits found");
113   }
114
115   return $query_result->{hits}{hits};
116}
117
118sub get_query_result_timed_out {
119   my $self = shift;
120   my ($query_result) = @_;
121
122   my $run = $self->context->do('$RUN');
123   $query_result ||= $run;
124   $self->brik_help_run_undef_arg('get_query_result_timed_out', $query_result) or return;
125   $self->brik_help_run_invalid_arg('get_query_result_timed_out', $query_result, 'HASH')
126      or return;
127
128   if (! exists($query_result->{timed_out})) {
129      return $self->log->error("get_query_result_timed_out: invalid query result, ".
130         "no timed_out found");
131   }
132
133   return $query_result->{timed_out} ? 1 : 0;
134}
135
136sub get_query_result_took {
137   my $self = shift;
138   my ($query_result) = @_;
139
140   my $run = $self->context->do('$RUN');
141   $query_result ||= $run;
142   $self->brik_help_run_undef_arg('get_query_result_took', $query_result) or return;
143   $self->brik_help_run_invalid_arg('get_query_result_took', $query_result, 'HASH')
144      or return;
145
146   if (! exists($query_result->{took})) {
147      return $self->log->error("get_query_result_took: invalid query result, no took found");
148   }
149
150   return $query_result->{took};
151}
152
153sub query {
154   my $self = shift;
155   my ($q, $index, $type) = @_;
156
157   $index ||= '*';
158   $type ||= '*';
159   $self->brik_help_run_undef_arg('query', $q) or return;
160
161   my $ce = $self->create_client or return;
162
163   my $r = $self->SUPER::query($q, $index, $type) or return;
164   if (defined($r)) {
165      if (exists($r->{hits}{total})) {
166         return $r;
167      }
168      else {
169         return $self->log->error("query: failed with [$r]");
170      }
171   }
172
173   return $self->log->error("query: failed");
174}
175
176#
177# run client::elasticsearch::query term domain=example.com index1-*,index2-*
178#
179sub term {
180   my $self = shift;
181   my ($kv, $index, $type) = @_;
182
183   $index ||= $self->index;
184   $type ||= $self->type;
185   $self->brik_help_run_undef_arg('term', $kv) or return;
186   $self->brik_help_set_undef_arg('term', $index) or return;
187   $self->brik_help_set_undef_arg('term', $type) or return;
188
189   if ($kv !~ /^\S+?=.+$/) {
190      return $self->log->error("term: kv must be in the form 'key=value'");
191   }
192   my ($key, $value) = split('=', $kv);
193
194   $self->debug && $self->log->debug("term: key[$key] value[$value]");
195
196   # Optimized version on ES 5.0.0
197   my $q = {
198      query => {
199         bool => {
200            must => { term => { $key => $value } },
201         },
202      },
203   };
204
205   $self->log->verbose("term: keys [$key] value [$value] index [$index] type [$type]");
206
207   return $self->query($q, $index, $type);
208}
209
210#
211# run client::elasticsearch::query unique_term ip domain=example.com index1-*,index2-*
212#
213sub unique_term {
214   my $self = shift;
215   my ($unique, $kv, $index, $type) = @_;
216
217   $index ||= $self->index;
218   $type ||= $self->type;
219   $self->brik_help_run_undef_arg('unique_term', $unique) or return;
220   $self->brik_help_run_undef_arg('unique_term', $kv) or return;
221   $self->brik_help_set_undef_arg('unique_term', $index) or return;
222   $self->brik_help_set_undef_arg('unique_term', $type) or return;
223
224   if ($kv !~ m{^.+?=.+$}) {
225      return $self->log->error("unique_term: kv [$kv] must be in the form ".
226         "'key=value'");
227   }
228   my ($key, $value) = split('=', $kv);
229
230   $self->debug && $self->log->debug("unique_term: key[$key] value[$value]");
231
232   # Optimized version on ES 5.0.0
233   my $q = {
234      size => 0,
235      query => {
236         bool => {
237            must => { term => { $key => $value } },
238         },
239      },
240      aggs => {
241         1 => {
242            cardinality => {
243               field => $unique,
244               precision_threshold => 40000,
245            },
246         },
247      },
248   };
249
250   $self->log->verbose("unique_term: unique [$unique] keys [$key] value [$value] ".
251      "index [$index] type [$type]");
252
253   return $self->query($q, $index, $type);
254}
255
256#
257#
258#
259sub unique_values {
260   my $self = shift;
261   my ($field, $index, $type) = @_;
262
263   $index ||= $self->index;
264   $type ||= $self->type;
265   $self->brik_help_run_undef_arg('unique_values', $field) or return;
266   $self->brik_help_set_undef_arg('unique_values', $index) or return;
267   $self->brik_help_set_undef_arg('unique_values', $type) or return;
268
269   my $size = $self->size * 10;
270
271   # Will return 10*100000=1_000_000 unique values.
272   my $q = {
273      aggs => {
274         1 => {
275            terms => {
276               field => $field,
277               include => { num_partitions => 10, partition => 0 },
278               size => $size,
279            },
280         },
281      },
282      size => 0,
283   };
284
285   $self->log->verbose("unique_values: unique [$field] index [$index] type [$type]");
286
287   return $self->query($q, $index, $type);
288}
289
290sub wildcard {
291   my $self = shift;
292   my ($kv, $index, $type) = @_;
293
294   $index ||= $self->index;
295   $type ||= $self->type;
296   $self->brik_help_run_undef_arg('wildcard', $kv) or return;
297   $self->brik_help_set_undef_arg('wildcard', $index) or return;
298   $self->brik_help_set_undef_arg('wildcard', $type) or return;
299
300   if ($kv !~ /^\S+?=.+$/) {
301      return $self->log->error("wildcard: kv must be in the form 'key=value'");
302   }
303   my ($key, $value) = split('=', $kv);
304
305   my $q = {
306      query => {
307         #constant_score => {  # Does not like constant_score
308            #filter => {
309               wildcard => {
310                  $key => $value,
311               },
312            #},
313         #},
314      },
315   };
316
317   $self->log->verbose("wildcard: keys [$key] value [$value] index [$index] type [$type]");
318
319   return $self->query($q, $index, $type);
320}
321
322#
323# run client::elasticsearch::query range ip_range.from=192.168.255.36 ip_range.to=192.168.255.36
324#
325sub range {
326   my $self = shift;
327   my ($kv_from, $kv_to, $index, $type) = @_;
328
329   $index ||= $self->index;
330   $type ||= $self->type;
331   $self->brik_help_run_undef_arg('range', $kv_from) or return;
332   $self->brik_help_run_undef_arg('range', $kv_to) or return;
333   $self->brik_help_set_undef_arg('range', $index) or return;
334   $self->brik_help_set_undef_arg('range', $type) or return;
335
336   if ($kv_from !~ /^\S+?=.+$/) {
337      return $self->log->error("range: kv_from [$kv_from] must be in the form 'key=value'");
338   }
339   if ($kv_to !~ /^\S+?=.+$/) {
340      return $self->log->error("range: kv_to [$kv_to] must be in the form 'key=value'");
341   }
342   my ($key_from, $value_from) = split('=', $kv_from);
343   my ($key_to, $value_to) = split('=', $kv_to);
344
345   #
346   # http://stackoverflow.com/questions/40519806/no-query-registered-for-filtered
347   # https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-filtered-query.html
348   # Compatible with ES 5.0
349   #
350   my $q = {
351      query => {
352         bool => {
353            must => [
354               { range => { $key_to => { gte => $value_to } } },
355               { range => { $key_from => { lte => $value_from } } },
356            ],
357         },
358      },
359   };
360
361   return $self->query($q, $index, $type);
362}
363
364#
365# run client::elasticsearch::query top name=10 users-*
366#
367sub top {
368   my $self = shift;
369   my ($kv_count, $index, $type) = @_;
370
371   $index ||= $self->index;
372   $type ||= $self->type;
373   $self->brik_help_run_undef_arg('top', $kv_count) or return;
374   $self->brik_help_set_undef_arg('top', $index) or return;
375   $self->brik_help_set_undef_arg('top', $type) or return;
376
377   if ($kv_count !~ /^\S+=\d+$/) {
378      return $self->log->error("top: kv_count [$kv_count] must be in the form 'key=value'");
379   }
380   my ($key_count, $value_count) = split('=', $kv_count);
381
382   my $q = {
383      aggs => {
384         top_values => {
385            terms => {
386               field => $key_count,
387               size => int($value_count),
388               order => { _count => 'desc' },
389            },
390         },
391      },
392   };
393
394   $self->log->verbose("top: key [$key_count] value [$value_count] index [$index] type [$type]");
395
396   return $self->query($q, $index, $type);
397}
398
399sub match_phrase {
400   my $self = shift;
401   my ($kv, $index, $type) = @_;
402
403   $index ||= $self->index;
404   $type ||= $self->type;
405   $self->brik_help_run_undef_arg('match_phrase', $kv) or return;
406   $self->brik_help_set_undef_arg('match_phrase', $index) or return;
407   $self->brik_help_set_undef_arg('match_phrase', $type) or return;
408
409   if ($kv !~ /^\S+?=.+$/) {
410      return $self->log->error("match_phrase: kv must be in the form 'key=value'");
411   }
412   my ($key, $value) = split('=', $kv);
413
414   $self->debug && $self->log->debug("match_phrase: key[$key] value[$value]");
415
416   my $q = {
417      query => {
418         match_phrase => {
419            $key => $value,
420         },
421      },
422   };
423
424   return $self->query($q, $index, $type);
425}
426
427sub match {
428   my $self = shift;
429   my ($kv, $index, $type) = @_;
430
431   $index ||= $self->index;
432   $type ||= $self->type;
433   $self->brik_help_run_undef_arg('match', $kv) or return;
434   $self->brik_help_set_undef_arg('match', $index) or return;
435   $self->brik_help_set_undef_arg('match', $type) or return;
436
437   if ($kv !~ /^\S+?=.+$/) {
438      return $self->log->error("match: kv must be in the form 'key=value'");
439   }
440   my ($key, $value) = split('=', $kv);
441
442   $self->debug && $self->log->debug("match: key[$key] value[$value]");
443
444   my $q = {
445      query => {
446         match => {
447            $key => $value,
448         },
449      },
450   };
451
452   return $self->query($q, $index, $type);
453}
454
455#
456# run client::elasticsearch::query top_match domain=10 host=*www*
457#
458sub top_match {
459   my $self = shift;
460   my ($kv_count, $kv_match, $index, $type) = @_;
461
462   $index ||= $self->index;
463   $type ||= $self->type;
464   $self->brik_help_run_undef_arg('top_match', $kv_count) or return;
465   $self->brik_help_run_undef_arg('top_match', $kv_match) or return;
466   $self->brik_help_set_undef_arg('top_match', $index) or return;
467   $self->brik_help_set_undef_arg('top_match', $type) or return;
468
469   if ($kv_count !~ /^\S+?=.+$/) {
470      return $self->log->error("top_match: kv_count [$kv_count] must be in the form 'key=value'");
471   }
472   if ($kv_match !~ /^\S+?=.+$/) {
473      return $self->log->error("top_match: kv_match [$kv_match] must be in the form 'key=value'");
474   }
475   my ($key_count, $value_count) = split('=', $kv_count);
476   my ($key_match, $value_match) = split('=', $kv_match);
477
478   my $q = {
479      query => {
480         #constant_score => {   # Does not like constant_score
481            #filter => {
482               match => {
483                  $key_match => $value_match,
484               },
485            #},
486         #},
487      },
488      aggs => {
489         top_values => {
490            terms => {
491               field => $key_count,
492               size => int($value_count),
493            },
494         },
495      },
496   };
497
498   return $self->query($q, $index, $type);
499}
500
501sub from_json_file {
502   my $self = shift;
503   my ($file, $index, $type) = @_;
504
505   $index ||= $self->index;
506   $type ||= $self->type;
507   $self->brik_help_run_undef_arg('from_json_file', $file) or return;
508   $self->brik_help_run_file_not_found('from_json_file', $file) or return;
509   $self->brik_help_set_undef_arg('from_json_file', $index) or return;
510   $self->brik_help_set_undef_arg('from_json_file', $type) or return;
511
512   my $fj = Metabrik::File::Json->new_from_brik_init($self) or return;
513   my $q = $fj->read($file) or return;
514
515   if (defined($q) && length($q)) {
516      return $self->query($q, $index, $type);
517   }
518
519   return $self->log->error("from_json_file: nothing to read from this file [$file]");
520}
521
522sub from_dump_file {
523   my $self = shift;
524   my ($file, $index, $type) = @_;
525
526   $index ||= $self->index;
527   $type ||= $self->type;
528   $self->brik_help_run_undef_arg('from_dump_file', $file) or return;
529   $self->brik_help_run_file_not_found('from_dump_file', $file) or return;
530   $self->brik_help_set_undef_arg('from_dump_file', $index) or return;
531   $self->brik_help_set_undef_arg('from_dump_file', $type) or return;
532
533   my $fd = Metabrik::File::Dump->new_from_brik_init($self) or return;
534   my $q = $fd->read($file) or return;
535
536   my $first = $q->[0];
537   if (defined($first)) {
538      return $self->query($first, $index, $type);
539   }
540
541   return $self->log->error("from_dump_file: nothing to read from this file [$file]");
542}
543
5441;
545
546__END__
547
548=head1 NAME
549
550Metabrik::Client::Elasticsearch::Query - client::elasticsearch::query Brik
551
552=head1 COPYRIGHT AND LICENSE
553
554Copyright (c) 2014-2017, Patrice E<lt>GomoRE<gt> Auffret
555
556You may distribute this module under the terms of The BSD 3-Clause License.
557See LICENSE file in the source distribution archive.
558
559=head1 AUTHOR
560
561Patrice E<lt>GomoRE<gt> Auffret
562
563=cut
Note: See TracBrowser for help on using the repository browser.