From d8bb089ec614d074489fd6a5b2faa606c01eae3a Mon Sep 17 00:00:00 2001 From: Andrew Rioux Date: Mon, 27 Oct 2025 13:32:27 -0400 Subject: [PATCH 01/10] fix: add docs for run_closure and remove debug log --- lib/LUCCDC/Jiujitsu/Util/DownloadContainer.pm | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/LUCCDC/Jiujitsu/Util/DownloadContainer.pm b/lib/LUCCDC/Jiujitsu/Util/DownloadContainer.pm index 985017e..e7851fb 100644 --- a/lib/LUCCDC/Jiujitsu/Util/DownloadContainer.pm +++ b/lib/LUCCDC/Jiujitsu/Util/DownloadContainer.pm @@ -18,6 +18,8 @@ $VERSION = 1.00; # on the default interface of the host system # - run_command: Given a command and a container name (container name optional), # runs the given command in the network namespace of the container +# - run_closure: Given a closure and a container name (container name optional), +# runs the closure in the network namespace of the container # - destroy_container: Given the namespace name, performs cleanup and deletes the # namespace. This is important to run, as resources exist outside of the process # and persist when jiujitsu is done executing @@ -109,14 +111,15 @@ sub run_closure { open( my $new_netns, '<', "/run/netns/$ns" ) or die "Could not open target net namespace"; - print syscall( 308, fileno($new_netns), 0 ), "\n"; + syscall( 308, fileno($new_netns), 0 ); + close($new_netns); my $return_value = $closure->(); - print syscall( 308, fileno($current_netns), 0 ), "\n"; + syscall( 308, fileno($current_netns), 0 ); close($current_netns); - close($new_netns); + return $return_value; } From 5558f5ff77416fda4aea45db80941bf539a70e64 Mon Sep 17 00:00:00 2001 From: Andrew Rioux Date: Mon, 27 Oct 2025 13:32:45 -0400 Subject: [PATCH 02/10] feat: add header function to display green text --- lib/LUCCDC/Jiujitsu/Util/Logging.pm | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/lib/LUCCDC/Jiujitsu/Util/Logging.pm b/lib/LUCCDC/Jiujitsu/Util/Logging.pm index 3e56cef..9d4cfc3 100644 --- a/lib/LUCCDC/Jiujitsu/Util/Logging.pm +++ b/lib/LUCCDC/Jiujitsu/Util/Logging.pm @@ -4,7 +4,7 @@ use parent qw(Exporter); use vars qw($VERSION @EXPORT_OK %EXPORT_TAGS); $VERSION = 1.00; -@EXPORT_OK = qw(error warning message %CR); +@EXPORT_OK = qw(error warning message header %CR); %EXPORT_TAGS = ( DEFAULT => \@EXPORT_OK, ); our %CR = ( @@ -19,22 +19,34 @@ our %CR = ( nocolor => "\033[0m", ); +sub header { + my ( $message, $header ) = @_; + $header ||= "--- "; + + print $CR{green}, $header, $message, $CR{nocolor}; + + return; +} + sub error { my ($message) = @_; - return $CR{red} . $message . $CR{nocolor}; + print $CR{red}, $message, $CR{nocolor}; + return; } sub warning { my ($message) = @_; - return $CR{yellow} . $message . $CR{nocolor}; + print $CR{yellow}, $message, $CR{nocolor}; + return; } sub message { my ($message) = @_; - return $message; + print $message; + return; } 1; From 373c99dd49dfeec921cac6d9926950994b202a4d Mon Sep 17 00:00:00 2001 From: Andrew Rioux Date: Mon, 27 Oct 2025 13:32:52 -0400 Subject: [PATCH 03/10] WIP: add ELK command to install ELK --- lib/LUCCDC/Jiujitsu.pm | 2 + lib/LUCCDC/Jiujitsu/Commands/elk.pm | 383 ++++++++++++++++++++++++++++ 2 files changed, 385 insertions(+) create mode 100644 lib/LUCCDC/Jiujitsu/Commands/elk.pm diff --git a/lib/LUCCDC/Jiujitsu.pm b/lib/LUCCDC/Jiujitsu.pm index b8983b2..2d198a6 100644 --- a/lib/LUCCDC/Jiujitsu.pm +++ b/lib/LUCCDC/Jiujitsu.pm @@ -10,6 +10,7 @@ use LUCCDC::Jiujitsu::Commands::ports; use LUCCDC::Jiujitsu::Commands::stat; use LUCCDC::Jiujitsu::Commands::file; use LUCCDC::Jiujitsu::Commands::downloadshell; +use LUCCDC::Jiujitsu::Commands::elk; # ABSTRACT: CLI to manage Linux # VERSION @@ -34,6 +35,7 @@ my %subcommands = ( 'useradd' => \&LUCCDC::Jiujitsu::Commands::useradd::run, 'ssh' => \&LUCCDC::Jiujitsu::Commands::SSH::run, 'stat' => \&LUCCDC::Jiujitsu::Commands::stat::run, + 'elk' => \&LUCCDC::Jiujitsu::Commands::elk::run, '--version' => sub { print "version"; exit; }, '--usage' => sub { print "usage"; exit; }, '--help' => \&help, diff --git a/lib/LUCCDC/Jiujitsu/Commands/elk.pm b/lib/LUCCDC/Jiujitsu/Commands/elk.pm new file mode 100644 index 0000000..b9ee709 --- /dev/null +++ b/lib/LUCCDC/Jiujitsu/Commands/elk.pm @@ -0,0 +1,383 @@ +package LUCCDC::Jiujitsu::Commands::elk; +use strictures 2; + +use Carp; +use File::Path qw(make_path); +use POSIX ":sys_wait_h"; + +use LUCCDC::Jiujitsu::Util::Logging qw(message error warning header); +use LUCCDC::Jiujitsu::Util::Linux::PerDistro qw(platform); +use LUCCDC::Jiujitsu::Util::Arguments qw(&parser); +use LUCCDC::Jiujitsu::Util::DownloadContainer + qw(&create_container &run_closure &destroy_container); + +# Set aside string so that I can use the literal +# ${path.config} in heredoc strings +my $path_config = '${path.config}'; + +my $elastic_password = ""; + +sub get_elastic_password { + if ( $elastic_password eq "" ) { + `stty -echo`; + print "Enter the password for the elastic user: "; + $elastic_password = ; + `stty echo`; + chomp $elastic_password; + } + return $elastic_password; +} + +my @options = ( + { + name => 'elastic_version', + flag => '--esver|-V', + val => '9.2.0', + type => 'string' + }, + { + name => 'download_url', + flag => '--download-url', + val => 'https://artifacts.elastic.co/downloads', + type => 'string' + }, + { + name => 'beats_download_url', + flag => '--beats-download-url', + val => 'https://artifacts.elastic.co/downloads/beats', + type => 'string' + }, + { + name => 'elasticsearch_share_directory', + flag => '--es-share-dir|-S', + val => '/opt/es', + type => 'string' + }, + { + name => 'elk_ip', + flag => '--elk-ip|-i', + val => '127.0.0.1', + type => 'string' + }, + { + name => 'elk_share_port', + flag => '--elk-share-port|-p', + val => 8000, + type => 'number' + }, + { + name => 'download_shell', + flag => '--use-download-shell|-d', + val => 0, + type => 'flag' + }, + { + name => 'sneaky_ip', + flag => '--sneaky-ip|-I', + val => '', + type => 'string' + }, +); + +my %subcommands = ( + '--help' => \&help, + '-h' => \&help, + 'help' => \&help, + 'install' => \&install, + 'setupzram' => \&setup_zram, + 'downloadpackages' => \&download_packages, + 'installpackages' => \&install_packages, + 'setupelastic' => \&setup_elasticsearch, + 'setupkibana' => \&setup_kibana, + 'setuplogstash' => \&setup_logstash, +); + +my %helpcommands = ( + '--help' => \&help, + '-h' => \&help +); + +my $toplevel_parser = parser( \@options, \%subcommands ); +my $sub_parser = parser( \@options, \%helpcommands ); + +sub run { + my @cmdline = @_; + + die "You must be root to install ELK" unless $> == 0; + die "A hostname must be set!" + if `hostnamectl` =~ qr/Static\+hostname:\s+\(unset\)/xms; + + $toplevel_parser->(@cmdline); + + exit; +} + +sub help { + +} + +sub install { + my @cmdline = @_; + + setup_zram(@cmdline); + download_packages(@cmdline); + install_packages(@cmdline); + setup_elasticsearch(@cmdline); + setup_kibana(@cmdline); + setup_logstash(@cmdline); + wait_for_kibana(@cmdline); + setup_auditbeat(@cmdline); + setup_filebeat(@cmdline); + setup_packetbeat(@cmdline); + + exit; +} + +sub setup_zram { + if ( !( `lsmod` =~ qr/zram/ ) ) { + `modprobe zram` + or return warning("??? Could not load zram\n"); + `zramctl /dev/zram0 --size=4G` + or return warning("??? Could not initialize /dev/zram0\n"); + `mkswap /dev/zram0` + or return warning("??? Could not initialize zram swap space\n"); + `swapon --priority=100 /dev/zram0` + or return warning("??? Could not enable zram swap space\n"); + + header("ZRAM has been set up!"); + } + else { + header("Skipping ZRAM setup"); + } + + return; +} + +sub download_file { + my ( $url, $file, $done_msg ) = @_; + + if (`which curl 2>/dev/null`) { + my $pid = fork(); + return $pid if $pid != 0; + + `curl -o $file $url 2>/dev/null >/dev/null`; + message($done_msg); + exit; + } + elsif (`which wget 2>/dev/null`) { + my $pid = fork(); + return $pid if $pid != 0; + + `wget -O $file $url 2>/dev/null >/dev/null`; + message($done_msg); + exit; + } + + error("!!! Could not find program to download files with"); + die; +} + +sub download_packages_internal { + my (%args) = @_; + + my $es_dir = $args{"elasticsearch_share_directory"}; + my $dl_url = $args{"download_url"}; + my $bdl_url = $args{"beats_download_url"}; + my $es_ver = $args{"elastic_version"}; + + make_path($es_dir); + chdir($es_dir); + + my @pids = (); + + header "Downloading elastic packages...\n"; + + if ( platform() eq "debian" ) { + foreach my $pkg (qw(elasticsearch logstash kibana)) { + message "Downloading $pkg deb...\n"; + push @pids, + ( + download_file( + "$dl_url/$pkg/$pkg-$es_ver-amd64.deb", "$pkg.deb", + "Done downloading $pkg!\n" + ) + ); + } + } + else { + foreach my $pkg (qw(elasticsearch logstash kibana)) { + message "Downloading $pkg rpm...\n"; + push @pids, + ( + download_file( + "$dl_url/$pkg/$pkg-$es_ver-x86_64.rpm", "$pkg.rpm", + "Done downloading $pkg!\n" + ) + ); + } + } + + foreach my $beat (qw(auditbeat filebeat packetbeat)) { + message "Downloading $beat rpm and deb...\n"; + + push @pids, + ( + download_file( + "$bdl_url/$beat/$beat-$es_ver-amd64.deb", "$beat.deb", + "Done downloading $beat deb!\n" + ), + download_file( + "$bdl_url/$beat/$beat-$es_ver-x86_64.rpm", "$beat.rpm", + "Done downloading $beat rpm!\n" + ) + ); + } + + foreach my $pid (@pids) { + waitpid( $pid, 0 ); + } + + header "Done downloading elastic packages!"; + + return; +} + +sub download_packages { + my @cmdline = @_; + my %args = $sub_parser->(@cmdline); + + if ( $args{"download_shell"} ) { + my $ns = create_container( $args{"sneaky_ip"} ); + my $val = run_closure( + sub { + download_packages_internal(%args); + }, + $ns + ); + destroy_container($ns); + return $val; + } + else { + return download_packages_internal(%args); + } +} + +sub install_packages { + my @cmdline = @_; + my %args = $sub_parser->(@cmdline); + + my $es_dir = $args{"elasticsearch_share_directory"}; + + header "Installing elastic packages...\n"; + + chdir $es_dir; + + if ( platform() eq "debian" ) { + foreach my $pkg ( + qw(elasticsearch logstash kibana filebeat auditbeat packetbeat)) + { + message "Installing $pkg...\n"; + `dpkg -i $pkg.deb`; + } + } + else { + foreach my $pkg ( + qw(elasticsearch logstash kibana filebeat auditbeat packetbeat)) + { + message "Installing $pkg...\n"; + `rpm -i $pkg.rpm`; + } + } + + header "Done installing packages\n"; + + return; +} + +sub setup_elasticsearch { + my @cmdline = @_; + my %args = $sub_parser->(@cmdline); + + my $es_dir = $args{"elasticsearch_share_directory"}; + + header "Configuring Elasticsearch\n"; + + my $es_password = get_elastic_password(); + + `systemctl enable --now elasticsearch`; + + open my $cmd, '|-', + '/usr/share/elasticsearch/bin/elasticsearch-reset-password -u elastic -i' + or die $!; + + print $cmd "y\n"; + print $cmd $es_password, "\n"; + print $cmd $es_password, "\n"; + + close $cmd; + + make_path("/etc/es_certs"); + `cp /etc/elasticsearch/certs/http_ca.crt /etc/es_certs`; + `cp /etc/elasticsearch/certs/http_ca.crt $es_dir`; + `chmod +r /etc/es_certs/http_ca.crt`; + `chmod +r $es_dir/http_ca.crt`; + + header "Elasticsearch configured!\n"; + + return; +} + +sub setup_kibana { + my @cmdline = @_; + my %args = $sub_parser->(@cmdline); + + header "Configuring Kibana\n"; + + my $token = +`/usr/share/elasticsearch/bin/elasticsearch-create-enrollment-token -s kibana`; + `sudo -u kibana /usr/share/kibana/bin/kibana-setup -t $token`; + + open my $in, "<", "/etc/kibana/kibana.yml" or die $!; + open my $out, ">", "/tmp/kibana.yml" or die $!; + while (<$in>) { + s/.*server.host:.*/server.host: "0.0.0.0"/xms; + print $out $_; + } + close $in; + close $out; + rename '/tmp/kibana.yml', '/etc/kibana/kibana.yml' or die $!; + + `systemctl enable --now kibana`; + + header "Kibana configured!\n"; + + return; +} + +sub setup_logstash { + header "Configuring Logstash\n"; + + return; +} + +sub wait_for_kibana { + +} + +sub setup_auditbeat { + +} + +sub setup_filebeat { + +} + +sub setup_packetbeat { + +} + +sub install_beats { + +} + +1; From c0d5291a908d4da83e8031df1b351794975cbace Mon Sep 17 00:00:00 2001 From: Andrew Rioux Date: Mon, 27 Oct 2025 15:45:01 -0400 Subject: [PATCH 04/10] feat: install beats and run setup commands --- lib/LUCCDC/Jiujitsu/Commands/elk.pm | 425 +++++++++++++++++++++++++++- 1 file changed, 423 insertions(+), 2 deletions(-) diff --git a/lib/LUCCDC/Jiujitsu/Commands/elk.pm b/lib/LUCCDC/Jiujitsu/Commands/elk.pm index b9ee709..19df1f0 100644 --- a/lib/LUCCDC/Jiujitsu/Commands/elk.pm +++ b/lib/LUCCDC/Jiujitsu/Commands/elk.pm @@ -3,6 +3,7 @@ use strictures 2; use Carp; use File::Path qw(make_path); +use IPC::Open2; use POSIX ":sys_wait_h"; use LUCCDC::Jiujitsu::Util::Logging qw(message error warning header); @@ -62,7 +63,7 @@ my @options = ( { name => 'elk_share_port', flag => '--elk-share-port|-p', - val => 8000, + val => 8080, type => 'number' }, { @@ -100,6 +101,150 @@ my %helpcommands = ( my $toplevel_parser = parser( \@options, \%subcommands ); my $sub_parser = parser( \@options, \%helpcommands ); +my $AUDITBEAT_BASE_CONFIG = <<'EOD'; +auditbeat.modules: +- module: auditd + audit_rules: | + # Executions + -a always,exit -F arch=b64 -S execve,execveat -k exec + -a always,exit -F arch=b64 -S clone,clone3 -k clones + + # Identity changes + -w /etc/group -p wa -k identity + -w /etc/passwd -p wa -k identity + -w /etc/gshadow -p wa -k identity + -w /etc/shadow -p wa -k identity + + # Unauthorized access attempts/enumeration + -a always,exit -F arch=b64 -S open,creat,truncat,ftruncate,openat,open_by_handle_at -F exit=-EACCESS -k access + -a always,exit -F arch=b64 -S open,creat,truncat,ftruncate,openat,open_by_handle_at -F exit=-EPERM -k access + -a always,exit -F arch=b64 -S geteuid,getuid,getegid,getgid -k potential_enum + + # Networking info + -a always,exit -F arch=b64 -S socket -k sockets + -a always,exit -F arch=b64 -S socket -F a0=17 -F a1=3 -k raw_sockets + + # Process injection + -a always,exit -F arch=b64 -S ptrace -k ptrace + +- module: file_integrity + paths: + - /bin + - /usr/bin + - /sbin + - /usr/sbin + - /etc + +- module: system + datasets: + - host + - login + - process + - socket + - user + state.period: 12h + user.detect_password_changes: true + login.wtmp_file_pattern: /var/log/wtmp* + login.btmp_file_pattern: /var/log/btmp* + +setup.template.settings.index.number_of_shards: 1 + +processors: + - add_host_metadata: ~ + - add_docker_metadata: ~ + +EOD + +my $PACKETBEAT_BASE_CONFIG = <<'EOD'; +packetbeat.interfaces.device: any +packetbeat.interfaces.poll_default_route: 1m +packetbeat.interfaces.internal_networks: + - private +packetbeat.flows: + timeout: 30s + period: 10s +packetbeat.protocols: +- type: icmp + enabled: true +- type: amqp + ports: [5672] +- type: cassandra + ports: [9042] +- type: dhcpv4 + ports: [67, 68] +- type: dns + ports: [53] +- type: http + ports: [80, 8080, 8000, 5000, 8002] +- type: memcache + ports: [11211] +- type: mysql + ports: [3306, 3307] +- type: pgsql + ports: [5432] +- type: redis + ports: [6379] +- type: thrift + ports: [9090] +- type: mongodb + ports: [27017] +- type: nfs + ports: [2049] +- type: tls + ports: + - 8443 +- type: sip + ports: [5060] + +setup.template.settings.index.number_of_shards: 1 + +processors: + - if.contains.tags: forwarded + then: + - drop_fields: + fields: [host] + else: + - add_host_metadata: ~ + - add_cloud_metadata: ~ + - add_docker_metadata: ~ + - detect_mime_type: + field: http.request.body.content + target: http.request.mime_type + - detect_mime_type: + field: http.response.body.content + target: http.response.mime_type + +EOD + +# This config is special; it will be used as is for both local beats installation and +# the central log server installation. It is intended to be injected into a file with +# text coming after it in an ambiguous format; on the central ELK server, it allows for +# injecting configuration to accept Cisco, Netflow, and Palo Alto logs, but on +# endpoints it allows for specifying other modules that aren't those 3 +my $FILEBEAT_BASE_CONFIG = <<'EOD'; +filebeat.inputs: +- type: udp + max_message_size: 10KiB + host: "0.0.0.0:514" + processors: + - syslog: + field: message + +processors: + - add_host_metadata: + when.not.contains.tags: forwarded + - add_docker_metadata: ~ + +setup.template.settings.index.number_of_shards: 1 + +filebeat.modules: + - module: system + syslog: + enabled: true + auth: + enabled: true +EOD + sub run { my @cmdline = @_; @@ -237,7 +382,7 @@ sub download_packages_internal { waitpid( $pid, 0 ); } - header "Done downloading elastic packages!"; + header "Done downloading elastic packages!\n"; return; } @@ -354,30 +499,306 @@ sub setup_kibana { return; } +sub logstash_config_file { + my ( $id, $key ) = @_; + + return <<"EOD"; +input { + beats { + port => 5044 + } +} + +output { + if [\@metadata][beat] == "winlogbeat" { + elasticsearch { + hosts => "https://localhost:9200" + manage_templates => false + action => "create" + ssl_enabled => true + ssl_certificate_authorities => "/etc/es_certs/http_ca.crt" + api_key => "$id:$key" + + pipeline => "%{[\@metadata][beat]}-%{[\@metadata][version]}-routing" + data_stream => true + } + } + + if [\@metadata][pipeline] { + elasticsearch { + hosts => "https://localhost:9200" + manage_templates => false + action => "create" + ssl_enabled => true + ssl_certificate_authorities => "/etc/es_certs/http_ca.crt" + api_key => "$id:$key" + + pipeline => "%{[\@metadata][pipeline]}" + data_stream => true + } + } + + elasticsearch { + hosts => "https://localhost:9200" + manage_templates => false + action => "create" + ssl_enabled => true + ssl_certificate_authorities => "/etc/es_certs/http_ca.crt" + api_key => "$id:$key" + + index => "%{[\@metadata][beat]}-%{[\@metadata][version]}" + } +} +EOD +} + sub setup_logstash { header "Configuring Logstash\n"; + my $es_password = get_elastic_password(); + + my $api_key_permissions_body = <<'EOD'; +{ + "name": "logstash-api-key", + "role_descriptors": { + "logstash_writer": { + "cluster": ["monitor","manage_index_templates","manage_ilm"], + "index": [{ + "names": ["filebeat-*","winlogbeat-*","auditbeat-*","packetbeat-*","logs-*"], + "privileges": ["view_index_metadata","read","create","manage","manage_ilm"] + }] + } + } +} +EOD + + open2( + my $api_out, + my $api_in, + "curl", + "-k", + "-u", + "elastic:$es_password", + "https://localhost:9200/_security/api_key?pretty", + "-X", + "POST", + "-H", + "content-type: application/json", + "-d", + $api_key_permissions_body + ); + + my ( $id, $key ); + while (<$api_out>) { + if ( $_ =~ qr/"id"\s*:\s*"([^"]+)"/xms ) { + $id = $1; + } + if ( $_ =~ qr/"api_key"\s*:\s*"([^"]+)"/xms ) { + $key = $1; + } + } + + open my $file, '>', '/etc/logstash/conf.d/pipeline.conf' or die $!; + print $file logstash_config_file( $id, $key ); + close $file; + + `systemctl enable --now logstash`; + + header "Logstash configured!\n"; + return; } sub wait_for_kibana { + header "Waiting for Kibana..."; + + while ( + !( + `curl http://localhost:5601/api/status 2>/dev/null` =~ + qr/"level":"available"/xms + ) + ) + { + message "Waiting for Kibana..."; + sleep 1; + } + return; } sub setup_auditbeat { + my @cmdline = @_; + my %args = $sub_parser->(@cmdline); + header "Setting up auditbeat"; + + my $es_password = get_elastic_password(); + + my $es_dir = $args{"elasticsearch_share_directory"}; + my $elk_ip = $args{"elk_ip"}; + + open my $abyml, '>', '/etc/auditbeat/auditbeat.yml' or die $!; + print $abyml <<"EOD"; +$AUDITBEAT_BASE_CONFIG + +output.elasticsearch: + hosts: ["https://localhost:9200"] + transport: https + username: elastic + password: "$es_password" + ssl: + enabled: true + certificate_authorities: "/etc/es_certs/http_ca.crt" +EOD + close $abyml; + + system("auditbeat setup"); + + open my $abymlp, '>', '/etc/auditbeat/auditbeat.yml' or die $!; + print $abymlp <<"EOD"; +$AUDITBEAT_BASE_CONFIG + +output.logstash: + hosts: ["$elk_ip:5044"] +EOD + close $abymlp; + + `systemctl enable auditbeat`; + `systemctl restart auditbeat`; + + header "Auditbeat is set up"; + + return; } sub setup_filebeat { + my @cmdline = @_; + my %args = $sub_parser->(@cmdline); + + header "Setting up Filebeat"; + + my $es_password = get_elastic_password(); + my $es_dir = $args{"elasticsearch_share_directory"}; + my $elk_ip = $args{"elk_ip"}; + + my $fbymlbase = <<'EOD'; +$FILEBEAT_BASE_CONFIG + + - module: netflow + log: + enabled: true + var: + netflow_host: localhost + netflow_port: 2055 + internal_networks: + - private + + - module: panw + panos: + enabled: true + var.syslog_host: 0.0.0.0 + var.syslog_port: 9001 + var.log_level: 5 + + - module: cisco + ftd: + enabled: true + var.syslog_host: 0.0.0.0 + var.syslog_port: 9002 + var.log_level: 5 + +EOD + + open my $fbyml, '>', '/etc/filebeat/filebeat.yml' or die $!; + print $fbyml <<"EOD"; +$FILEBEAT_BASE_CONFIG + +output.elasticsearch: + hosts: ["https://localhost:9200"] + transport: https + username: "elastic" + password: "$es_password" + ssl: + enabled: true + certificate_authorities: "/etc/es_certs/http_ca.crt" +EOD + close $fbyml; + + `filebeat setup`; + + open my $fbymlp, '>', '/etc/filebeat/filebeat.yml' or die $!; + print $fbymlp <<"EOD"; +$FILEBEAT_BASE_CONFIG + +output.logstash: + hosts: ["$elk_ip:5044"] +EOD + close $fbymlp; + + `systemctl enable filebeat`; + `systemctl start filebeat`; + + header "Filebeat is set up"; + + return; } sub setup_packetbeat { + my @cmdline = @_; + my %args = $sub_parser->(@cmdline); + + header "Setting up Filebeat"; + + my $es_password = get_elastic_password(); + + my $es_dir = $args{"elasticsearch_share_directory"}; + my $elk_ip = $args{"elk_ip"}; + + open my $pbyml, '>', '/etc/packetbeat/packetbeat.yml' or die $!; + print $pbyml <<"EOD"; +$PACKETBEAT_BASE_CONFIG + +output.elasticsearch: + hosts: ["https://localhost:9200"] + transport: https + username: "elastic" + password: "$es_password" + ssl: + enabled: true + certificate_authorities: "/etc/es_certs/http_ca.crt" +EOD + close $pbyml; + + `packetbeat setup`; + open my $pbymlp, '>', '/etc/packetbeat/packetbeat.yml' or die $!; + print $pbymlp <<"EOD"; +$PACKETBEAT_BASE_CONFIG + +output.logstash: + hosts: ["$elk_ip:5044"] +EOD + close $pbymlp; + + `systemctl enable packetbeat`; + `systemctl start packetbeat`; + + header "Packetbeat is set up"; + + return; } sub install_beats { + my @cmdline = @_; + my %args = $sub_parser->(@cmdline); + my $elk_ip = $args{"elk_ip"}; + my $elk_share_port = $args{"elk_share_port"}; + my $download_shell = $args{"download_shell"}; + my $sneaky_ip = $args{"sneaky_ip"}; + + return; } 1; From c828cbb6cf2457cb07d870858619088cba9df1df Mon Sep 17 00:00:00 2001 From: Andrew Rioux Date: Mon, 27 Oct 2025 16:03:31 -0400 Subject: [PATCH 05/10] fix: use system instead of backticks show output as it happens to avoid appearing frozen --- lib/LUCCDC/Jiujitsu/Commands/elk.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/LUCCDC/Jiujitsu/Commands/elk.pm b/lib/LUCCDC/Jiujitsu/Commands/elk.pm index 19df1f0..c1bd3ad 100644 --- a/lib/LUCCDC/Jiujitsu/Commands/elk.pm +++ b/lib/LUCCDC/Jiujitsu/Commands/elk.pm @@ -725,7 +725,7 @@ output.elasticsearch: EOD close $fbyml; - `filebeat setup`; + system("filebeat setup"); open my $fbymlp, '>', '/etc/filebeat/filebeat.yml' or die $!; print $fbymlp <<"EOD"; @@ -770,7 +770,7 @@ output.elasticsearch: EOD close $pbyml; - `packetbeat setup`; + system("packetbeat setup"); open my $pbymlp, '>', '/etc/packetbeat/packetbeat.yml' or die $!; print $pbymlp <<"EOD"; From d746de792215b33c6263ae5b3235598a7153ecd3 Mon Sep 17 00:00:00 2001 From: Andrew Rioux Date: Mon, 27 Oct 2025 16:41:11 -0400 Subject: [PATCH 06/10] feat: add help and docs --- lib/LUCCDC/Jiujitsu/Commands/elk.pm | 67 +++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/lib/LUCCDC/Jiujitsu/Commands/elk.pm b/lib/LUCCDC/Jiujitsu/Commands/elk.pm index c1bd3ad..e70a787 100644 --- a/lib/LUCCDC/Jiujitsu/Commands/elk.pm +++ b/lib/LUCCDC/Jiujitsu/Commands/elk.pm @@ -85,12 +85,27 @@ my %subcommands = ( '-h' => \&help, 'help' => \&help, 'install' => \&install, + 'in' => \&install, 'setupzram' => \&setup_zram, + 'zr' => \&setup_zram, 'downloadpackages' => \&download_packages, + 'dpkg' => \&download_packages, 'installpackages' => \&install_packages, + 'ipkg' => \&install_packages, 'setupelastic' => \&setup_elasticsearch, + 'es' => \&setup_elasticsearch, 'setupkibana' => \&setup_kibana, + 'ki' => \&setup_kibana, 'setuplogstash' => \&setup_logstash, + 'lo' => \&setup_logstash, + 'setupauditbeat' => \&setup_auditbeat, + 'ab' => \&setup_auditbeat, + 'setuppacketbeat' => \&setup_packetbeat, + 'pb' => \&setup_packetbeat, + 'setupfilebeat' => \&setup_filebeat, + 'fb' => \&setup_filebeat, + 'beats' => \&install_beats, + 'installbeats' => \&install_beats ); my %helpcommands = ( @@ -258,7 +273,59 @@ sub run { } sub help { + my ($progname) = $0 =~ m{([^\/]+)$}xms; + + print <<"END_HELP"; +Installs and configures ELK or ELK dependent endpoints (beats) + +Usage: + $progname elk install # Installs an ELK stack + $progname elk install -d -I 10.0.2.16 # Installs an ELK stack using the download shell and a sneaky IP + $progname elk beats -i 192.168.128.53 # Installs beats to point to the ELK stack, downloading resources from the ELK share + $progname elk beats -i 192.168.128.53 -P 8000 # Installs beats, replacing the share port with 8000 instead of 8080 + $progname elk beats -i 192.168.128.53 -d -I 10.0.2.17 # Installs beats using the download shell and sneaky IP + +Common subcommand options: + -V, --esver=VERSION The version to use for ELK and beats packages + --download-url=https://artifacts.elastic.co/downloads The URL to download ELK packages from + --beats-download-url=https://artifacts.elastic.co/downloads/beats The URL to download ELK packages from + -S, --es-share-dir=/opt/es Where to store downloaded packages for redistribution, and where to place the CA certificate + -i, --elk-ip=IP The IP address of the ELK server + -P, --elk-share-port=IP The port of the share that a Python web server should be running from + -d, --use-download-shell Use the download shell when downloading packages + -I, --sneaky-ip=IP Sneaky IP to use when making use of the download shell + -h, --help Print this help message + +Subcommands: + in, install: Run through the full set up ELK setup commands; equivalent to zr, dpkg, ipkg, es, ki, lo, ab, pb, and fb + zr, setupzram: Enable 4GB of ZRAM based swap + dpkg, downloadpackages: Download the packages necessary to install ELK and the beats for both Debian and RHEL + ipkg, installpackages: Install the downloaded packages for the appropriate operating system + es, setupelastic: Configure Elasticsearch and ensure it is available with the appropriate password + ki, setupkibana: Configure Kibana to access Elasticsearch and allow it to be publicly available + lo, setuplogstash: Create an Elasticsearch API key and generate a logstash pipeline that uses both ECS and non-ECS dashboards and routes + ab, setupauditbeat: Configure Elasticsearch and Kibana to prepare them for auditbeat data, then configure this device as an endpoint sending auditbeat data + fb, setupfilebeat: Configure Elasticsearch and Kibana to prepare them for filebeat data, then configure this device as an endpoint sending filebeat data + pb, setuppacketbeat: Configure Elasticsearch and Kibana to prepare them for packetbeat data, then configure this device as an endpoint sending packetbeat data + beats, installbeats: Configure this device as an endpoint sending data to the specified ELK stack. Downloads packages from the ELK stack + +Configuration notes: + When installing and configuring ELK, the following ports will be opened up: + - 514/udp: Syslog input. Generic from Windows and Linux systems + - 2055/udp: Netflow input. Useful from network firewalls + - 5044/tcp: Beats input from endpoints + - 5601/tcp: Kibana web UI + - 8080/tcp: Python web server hosting packages for download + - 9001/udp: Palo Alto Syslog input + - 9002/udp: Cisco FTD Syslog input + - 9200/tcp: Elasticsearch. Useful to open for Windows to configure routing and indices + + The installation of beats will depend on a Python web server or similar running that can distribute files in the elasticsearch share, by default /opt/es + + Beats will require allowing outbound traffic to port 5044 on the ELK server from host and network based firewalls +END_HELP + return; } sub install { From 55b5b654526a751c0c64df9bb78c345143d8e098 Mon Sep 17 00:00:00 2001 From: Andrew Rioux Date: Mon, 27 Oct 2025 23:00:01 -0400 Subject: [PATCH 07/10] feat: finish beats setup command tested all ELK code optimized logstash setup fixed auditbeat config to prevent crashes --- lib/LUCCDC/Jiujitsu/Commands/elk.pm | 276 ++++++++++++++++++++-------- 1 file changed, 203 insertions(+), 73 deletions(-) diff --git a/lib/LUCCDC/Jiujitsu/Commands/elk.pm b/lib/LUCCDC/Jiujitsu/Commands/elk.pm index e70a787..88fbce3 100644 --- a/lib/LUCCDC/Jiujitsu/Commands/elk.pm +++ b/lib/LUCCDC/Jiujitsu/Commands/elk.pm @@ -131,8 +131,8 @@ auditbeat.modules: -w /etc/shadow -p wa -k identity # Unauthorized access attempts/enumeration - -a always,exit -F arch=b64 -S open,creat,truncat,ftruncate,openat,open_by_handle_at -F exit=-EACCESS -k access - -a always,exit -F arch=b64 -S open,creat,truncat,ftruncate,openat,open_by_handle_at -F exit=-EPERM -k access + -a always,exit -F arch=b64 -S open,creat,truncate,ftruncate,openat,open_by_handle_at -F exit=-EACCES -k access + -a always,exit -F arch=b64 -S open,creat,truncate,ftruncate,openat,open_by_handle_at -F exit=-EPERM -k access -a always,exit -F arch=b64 -S geteuid,getuid,getegid,getgid -k potential_enum # Networking info @@ -290,11 +290,11 @@ Common subcommand options: --download-url=https://artifacts.elastic.co/downloads The URL to download ELK packages from --beats-download-url=https://artifacts.elastic.co/downloads/beats The URL to download ELK packages from -S, --es-share-dir=/opt/es Where to store downloaded packages for redistribution, and where to place the CA certificate - -i, --elk-ip=IP The IP address of the ELK server - -P, --elk-share-port=IP The port of the share that a Python web server should be running from - -d, --use-download-shell Use the download shell when downloading packages - -I, --sneaky-ip=IP Sneaky IP to use when making use of the download shell - -h, --help Print this help message + -i, --elk-ip=IP The IP address of the ELK server + -P, --elk-share-port=IP The port of the share that a Python web server should be running from + -d, --use-download-shell Use the download shell when downloading packages + -I, --sneaky-ip=IP Sneaky IP to use when making use of the download shell + -h, --help Print this help message Subcommands: in, install: Run through the full set up ELK setup commands; equivalent to zr, dpkg, ipkg, es, ki, lo, ab, pb, and fb @@ -310,7 +310,7 @@ Subcommands: beats, installbeats: Configure this device as an endpoint sending data to the specified ELK stack. Downloads packages from the ELK stack Configuration notes: - When installing and configuring ELK, the following ports will be opened up: + When installing and configuring ELK, the following ports should be opened up: - 514/udp: Syslog input. Generic from Windows and Linux systems - 2055/udp: Netflow input. Useful from network firewalls - 5044/tcp: Beats input from endpoints @@ -331,17 +331,20 @@ END_HELP sub install { my @cmdline = @_; + get_elastic_password(); + setup_zram(@cmdline); download_packages(@cmdline); install_packages(@cmdline); setup_elasticsearch(@cmdline); setup_kibana(@cmdline); setup_logstash(@cmdline); - wait_for_kibana(@cmdline); setup_auditbeat(@cmdline); setup_filebeat(@cmdline); setup_packetbeat(@cmdline); + help(); + exit; } @@ -561,15 +564,26 @@ sub setup_kibana { `systemctl enable --now kibana`; + header "Waiting for Kibana...\n"; + + while ( + !( + `curl http://localhost:5601/api/status 2>/dev/null` =~ + qr/"level":"available"/xms + ) + ) + { + message "Waiting for Kibana...\n"; + sleep 1; + } + header "Kibana configured!\n"; return; } sub logstash_config_file { - my ( $id, $key ) = @_; - - return <<"EOD"; + return <<'EOD'; input { beats { port => 5044 @@ -577,43 +591,43 @@ input { } output { - if [\@metadata][beat] == "winlogbeat" { + if [@metadata][beat] == "winlogbeat" { elasticsearch { hosts => "https://localhost:9200" - manage_templates => false + manage_template => false action => "create" ssl_enabled => true ssl_certificate_authorities => "/etc/es_certs/http_ca.crt" - api_key => "$id:$key" + api_key => "${ES_API_KEY}" - pipeline => "%{[\@metadata][beat]}-%{[\@metadata][version]}-routing" + pipeline => "%{[@metadata][beat]}-%{[@metadata][version]}-routing" data_stream => true } } - if [\@metadata][pipeline] { + if [@metadata][pipeline] { elasticsearch { hosts => "https://localhost:9200" - manage_templates => false + manage_template => false action => "create" ssl_enabled => true ssl_certificate_authorities => "/etc/es_certs/http_ca.crt" - api_key => "$id:$key" + api_key => "${ES_API_KEY}" - pipeline => "%{[\@metadata][pipeline]}" + pipeline => "%{[@metadata][pipeline]}" data_stream => true } } elasticsearch { hosts => "https://localhost:9200" - manage_templates => false + manage_template => false action => "create" ssl_enabled => true ssl_certificate_authorities => "/etc/es_certs/http_ca.crt" - api_key => "$id:$key" + api_key => "${ES_API_KEY}" - index => "%{[\@metadata][beat]}-%{[\@metadata][version]}" + index => "%{[@metadata][beat]}-%{[@metadata][version]}" } } EOD @@ -622,9 +636,13 @@ EOD sub setup_logstash { header "Configuring Logstash\n"; - my $es_password = get_elastic_password(); + make_path("/etc/systemd/system/logstash.service.d"); + + if ( !-f "/etc/systemd/system/logstash.service.d/api_key.conf" ) { - my $api_key_permissions_body = <<'EOD'; + my $es_password = get_elastic_password(); + + my $api_key_permissions_body = <<'EOD'; { "name": "logstash-api-key", "role_descriptors": { @@ -639,65 +657,62 @@ sub setup_logstash { } EOD - open2( - my $api_out, - my $api_in, - "curl", - "-k", - "-u", - "elastic:$es_password", - "https://localhost:9200/_security/api_key?pretty", - "-X", - "POST", - "-H", - "content-type: application/json", - "-d", - $api_key_permissions_body - ); - - my ( $id, $key ); - while (<$api_out>) { - if ( $_ =~ qr/"id"\s*:\s*"([^"]+)"/xms ) { - $id = $1; - } - if ( $_ =~ qr/"api_key"\s*:\s*"([^"]+)"/xms ) { - $key = $1; + open2( + my $api_out, + my $api_in, + "curl", + "-k", + "-u", + "elastic:$es_password", + "https://localhost:9200/_security/api_key?pretty", + "-X", + "POST", + "-H", + "content-type: application/json", + "-d", + $api_key_permissions_body + ); + + my ( $id, $key ); + while (<$api_out>) { + if ( $_ =~ qr/"id"\s*:\s*"([^"]+)"/xms ) { + $id = $1; + } + if ( $_ =~ qr/"api_key"\s*:\s*"([^"]+)"/xms ) { + $key = $1; + } } + + open my $key_file, '>', + '/etc/systemd/system/logstash.service.d/api_key.conf' + or die $!; + + print $key_file <<"EOD"; +[Service] +Environment="ES_API_KEY=$id:$key" +EOD + + close $key_file; } open my $file, '>', '/etc/logstash/conf.d/pipeline.conf' or die $!; - print $file logstash_config_file( $id, $key ); + print $file logstash_config_file(); close $file; - `systemctl enable --now logstash`; + `systemctl daemon-reload`; + `systemctl enable logstash`; + `systemctl restart logstash`; header "Logstash configured!\n"; return; } -sub wait_for_kibana { - header "Waiting for Kibana..."; - - while ( - !( - `curl http://localhost:5601/api/status 2>/dev/null` =~ - qr/"level":"available"/xms - ) - ) - { - message "Waiting for Kibana..."; - sleep 1; - } - - return; -} - sub setup_auditbeat { my @cmdline = @_; my %args = $sub_parser->(@cmdline); - header "Setting up auditbeat"; + header "Setting up auditbeat\n"; my $es_password = get_elastic_password(); @@ -733,7 +748,7 @@ EOD `systemctl enable auditbeat`; `systemctl restart auditbeat`; - header "Auditbeat is set up"; + header "Auditbeat is set up\n"; return; } @@ -742,7 +757,7 @@ sub setup_filebeat { my @cmdline = @_; my %args = $sub_parser->(@cmdline); - header "Setting up Filebeat"; + header "Setting up Filebeat\n"; my $es_password = get_elastic_password(); @@ -806,7 +821,7 @@ EOD `systemctl enable filebeat`; `systemctl start filebeat`; - header "Filebeat is set up"; + header "Filebeat is set up\n"; return; } @@ -815,7 +830,7 @@ sub setup_packetbeat { my @cmdline = @_; my %args = $sub_parser->(@cmdline); - header "Setting up Filebeat"; + header "Setting up Packetbeat\n"; my $es_password = get_elastic_password(); @@ -851,7 +866,46 @@ EOD `systemctl enable packetbeat`; `systemctl start packetbeat`; - header "Packetbeat is set up"; + header "Packetbeat is set up\n"; + + return; +} + +sub download_beats { + my ( $elk_ip, $elk_share_port ) = @_; + + my @pids = (); + + if ( platform() eq "debian" ) { + foreach my $pkg (qw(auditbeat filebeat packetbeat)) { + message "Downloading $pkg deb...\n"; + push @pids, + ( + download_file( + "http://$elk_ip:$elk_share_port/$pkg.deb", + "/tmp/$pkg.deb", + "Done downloading $pkg!\n" + ) + ); + } + } + else { + foreach my $pkg (qw(auditbeat filebeat packetbeat)) { + message "Downloading $pkg deb...\n"; + push @pids, + ( + download_file( + "http://$elk_ip:$elk_share_port/$pkg.deb", + "/tmp/$pkg.deb", + "Done downloading $pkg!\n" + ) + ); + } + } + + foreach my $pid (@pids) { + waitpid( $pid, 0 ); + } return; } @@ -865,6 +919,82 @@ sub install_beats { my $download_shell = $args{"download_shell"}; my $sneaky_ip = $args{"sneaky_ip"}; + header "Downloading beats packages...\n"; + + if ($download_shell) { + my $ns = create_container($sneaky_ip); + run_closure( + sub { + download_beats( $elk_ip, $elk_share_port ); + }, + $ns + ); + destroy_container($ns); + } + else { + download_beats( $elk_ip, $elk_share_port ); + } + + header "Done downloading beats packages! Installing beats packages...\n"; + + foreach my $beat (qw(auditbeat filebeat packetbeat)) { + if ( platform() eq "debian" ) { + system("dpkg -i /tmp/$beat.deb"); + } + else { + system("rpm -i /tmp/$beat.rpm"); + } + } + + header "Done installing beats! Configuring now...\n"; + + open my $abyml, '>', '/etc/auditbeat/auditbeat.yml' or die $!; + print $abyml <<"EOD"; +$AUDITBEAT_BASE_CONFIG +output.logstash: + hosts: ["$elk_ip:5044"] +EOD + close $abyml; + + `systemctl enable auditbeat`; + `systemctl restart auditbeat`; + + message "Auditbeat is set up\n"; + + open my $pbyml, '>', '/etc/packetbeat/packetbeat.yml' or die $!; + print $pbyml <<"EOD"; +$PACKETBEAT_BASE_CONFIG +output.logstash: + hosts: ["$elk_ip:5044"] +EOD + close $pbyml; + + `systemctl enable packetbeat`; + `systemctl restart packetbeat`; + + message "Packetbeat is set up\n"; + + open my $fbyml, '>', '/etc/filebeat/filebeat.yml' or die $!; + print $fbyml <<"EOD"; +$FILEBEAT_BASE_CONFIG +output.logstash: + hosts: ["$elk_ip:5044"] +EOD + close $fbyml; + + `systemctl enable filebeat`; + `systemctl restart filebeat`; + + message "Filebeat is set up\n"; + + header "All beats should be set up! Verifying now...\n"; + + system("auditbeat test output"); + system("packetbeat test output"); + system("filebeat test output"); + + header "All set up!\n"; + return; } From f5ac02400e37406b4aac66409531f1ce95b52330 Mon Sep 17 00:00:00 2001 From: Andrew Rioux Date: Mon, 27 Oct 2025 23:13:15 -0400 Subject: [PATCH 08/10] fix: use appropriate error handling for zram setup --- lib/LUCCDC/Jiujitsu/Commands/elk.pm | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/LUCCDC/Jiujitsu/Commands/elk.pm b/lib/LUCCDC/Jiujitsu/Commands/elk.pm index 88fbce3..8ab74ab 100644 --- a/lib/LUCCDC/Jiujitsu/Commands/elk.pm +++ b/lib/LUCCDC/Jiujitsu/Commands/elk.pm @@ -350,19 +350,19 @@ sub install { sub setup_zram { if ( !( `lsmod` =~ qr/zram/ ) ) { - `modprobe zram` - or return warning("??? Could not load zram\n"); - `zramctl /dev/zram0 --size=4G` - or return warning("??? Could not initialize /dev/zram0\n"); + `modprobe zram`; + return warning("??? Could not load zram\n") if $? != 0; + `zramctl /dev/zram0 --size=4G`; + return warning("??? Could not initialize /dev/zram0\n") if $? != 0; `mkswap /dev/zram0` or return warning("??? Could not initialize zram swap space\n"); - `swapon --priority=100 /dev/zram0` - or return warning("??? Could not enable zram swap space\n"); + `swapon --priority=100 /dev/zram0`; + return warning("??? Could not enable zram swap space\n") if $? != 0; - header("ZRAM has been set up!"); + header("ZRAM has been set up!\n"); } else { - header("Skipping ZRAM setup"); + header("Skipping ZRAM setup\n"); } return; From 199d8c540bfcb552ae51daa217936d601b2f906e Mon Sep 17 00:00:00 2001 From: Andrew Rioux Date: Tue, 28 Oct 2025 10:48:49 -0400 Subject: [PATCH 09/10] feat: add auto loading Kibana dashboards --- .gitattributes | 1 + .perlcriticrc | 4 ++++ lib/LUCCDC/Jiujitsu/Commands/elk.pm | 20 +++++++++++++++++++- 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/.gitattributes b/.gitattributes index 2bb83d3..1f1fa7e 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4,3 +4,4 @@ flake.* export-ignore nix export-ignore .envrc export-ignore .perlcriticrc export-ignore +*.ndjson filter=lfs diff=lfs merge=lfs -text diff --git a/.perlcriticrc b/.perlcriticrc index 377ad66..10f7a40 100644 --- a/.perlcriticrc +++ b/.perlcriticrc @@ -2,3 +2,7 @@ severity = stern [InputOutput::RequireBriefOpen] lines = 20 + +[-InputOutput::ProhibitExplicitStdin] + +[-InputOutput::ProhibitBacktickOperators] diff --git a/lib/LUCCDC/Jiujitsu/Commands/elk.pm b/lib/LUCCDC/Jiujitsu/Commands/elk.pm index 8ab74ab..34abe35 100644 --- a/lib/LUCCDC/Jiujitsu/Commands/elk.pm +++ b/lib/LUCCDC/Jiujitsu/Commands/elk.pm @@ -2,7 +2,9 @@ package LUCCDC::Jiujitsu::Commands::elk; use strictures 2; use Carp; -use File::Path qw(make_path); +use Cwd qw(abs_path); +use File::Basename qw(dirname); +use File::Path qw(make_path); use IPC::Open2; use POSIX ":sys_wait_h"; @@ -25,6 +27,7 @@ sub get_elastic_password { $elastic_password = ; `stty echo`; chomp $elastic_password; + print "\n"; } return $elastic_password; } @@ -577,6 +580,21 @@ sub setup_kibana { sleep 1; } + header "Kibana online! Importing dashboards...\n"; + + my $base_path = dirname( abs_path(__FILE__) ) . "/elk"; + opendir my $dir, $base_path or die $!; + my @files = map { $_ =~ qr/^\.+$/xms ? () : $_ } readdir $dir; + close $dir; + + foreach my $dashboard (@files) { + my $es_password = get_elastic_password(); + + my $path_ref = '@' . "$base_path/$dashboard"; + +`curl -k -u elastic:$es_password http://localhost:5601/api/saved_objects/_import?overwrite=true -X POST -H "kbn-xsrf: true" --form file=$path_ref`; + } + header "Kibana configured!\n"; return; From 4fd173819e100285a58effbce81eae587dac98ed Mon Sep 17 00:00:00 2001 From: Andrew Rioux Date: Tue, 28 Oct 2025 10:51:20 -0400 Subject: [PATCH 10/10] feat: add Linux dashboard --- lib/LUCCDC/Jiujitsu/Commands/elk/linux.ndjson | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 lib/LUCCDC/Jiujitsu/Commands/elk/linux.ndjson diff --git a/lib/LUCCDC/Jiujitsu/Commands/elk/linux.ndjson b/lib/LUCCDC/Jiujitsu/Commands/elk/linux.ndjson new file mode 100644 index 0000000..457722d --- /dev/null +++ b/lib/LUCCDC/Jiujitsu/Commands/elk/linux.ndjson @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5d8865636ef1b9398f7ef853d65259a66bd5528225a92eecf748cd6a50da0ee8 +size 378121