App-Spoor

 view release on metacpan or  search on metacpan

lib/App/Spoor/Config.pm  view on Meta::CPAN

      transformer: bin/login_log_transformer.pl
    login:
      debug: 1
      maxinterval: 10
      name: /usr/local/cpanel/logs/login_log
      transformer: bin/login_log_transformer.pl
  transmission:
    credentials:
      api_identifier: api_identifier_goes_here
      api_secret: api_secret_goes_here
    endpoints:
      report: /api/reports
    host: https://spoor.capefox.co

=head1 SUBROUTINES/METHODS

=head2 get_follower_config

Gets the config related to a specific type of follower (access, error, login).

  $reference_to_access_config_hash = App::Spoor::Config::get_follower_config('access');

lib/App/Spoor/EntryTransmitter.pm  view on Meta::CPAN


  App::Spoor::EntryTransmitter::transmit(\%data, $user_agent, $transmission_config);

=cut

sub transmit {
  my $data = shift;
  my $ua = shift;
  my $config = shift;

  my $uri = $config->{host} . $config->{endpoints}{report};

  my $credentials = 'Basic ' . encode_base64(
    $config->{credentials}{api_identifier} . ':' . $config->{credentials}{api_secret}
  );

  my $content = to_json({
    report => {
      entries => [
        $data
      ],

lib/App/Spoor/Installer.pm  view on Meta::CPAN

          debug => 1,
          transformer => 'bin/login_log_transformer.pl',
        },
      },
      transmission => {
        credentials => {
          api_identifier => $install_parameters->{'api_identifier'},
          api_secret => $install_parameters->{'api_secret'},
        },
        host => 'https://spoor.capefox.co',
        endpoints => {
          report => '/api/reports',
        }
      }
    });

  $config->write("$root_path/etc/spoor/spoor.yml");
  chmod(0600, "$root_path/etc/spoor/spoor.yml");

  mkdir("$root_path/var/lib/spoor", 0700);
  mkdir("$root_path/var/lib/spoor/parsed", 0700);

lib/App/Spoor/LoginEntryParser.pm  view on Meta::CPAN

=item * ip: The ip logging in.

=item * status: Can be one of 'success', 'deferred' or 'failed'. 

=item * credential: The credential (email address/username) presented.

=item * possessor: In the case of an email address being provided, the domain user to which it belongs.

=item * message: This is only set if the entry contained additional info (generally on a non-successful login), e.g. "security token missing".

=item * endpoint: HTTP-related information, only present on a non-successful login.

=back

=cut

sub parse {
  use DateTime::Format::Strptime;

  my $log_entry = shift;
  my $date_parser = DateTime::Format::Strptime->new(pattern => '%Y-%m-%d %H:%M:%S %z', on_error => 'croak');

lib/App/Spoor/LoginEntryParser.pm  view on Meta::CPAN

    } else {
      $response{credential} = $+{credential_string};
    }
  } elsif ($log_entry =~ /
    \A
    \[(?<timestamp>[^\]]+)\]\s
    info\s
    \[(?<scope>[^\]]+)\]\s
    (?<ip>\S+)\s
    -\s(?<credential>[^-]+)\s
    "(?<endpoint>[^"]+)"\s
    (?<status>[A-Z]+)\s
    [^:]+:\s(?<message>.+)
    \Z
  /x) {
    %response = (
      type => 'login',
      event => 'login',
      log_time => $date_parser->parse_datetime($+{timestamp})->epoch(),
      scope => $+{scope},
      ip => $+{ip},
      status => lc($+{status}),
      credential => $+{credential},
      message => $+{message},
      endpoint => $+{endpoint}
    );
  }

  if ($response{scope} eq 'webmaild' && $response{credential} =~ /@/) {
    $response{context} = 'mailbox';
  } elsif ($response{scope} eq 'webmaild') {
    $response{context} = 'domain';
  } elsif ($response{scope} eq 'cpaneld') {
    $response{context} = 'domain';
  } elsif ($response{scope} eq 'whostmgrd') {

t/AccessEntryParser.t  view on Meta::CPAN

  \%parsed_log_entry_incorrect_verb,
  'Parses an access log entry - incorrect http verb'
);

is_deeply(
  App::Spoor::AccessEntryParser::parse("$access_log_entry_incorrect_verb\n"),
  \%parsed_log_entry_incorrect_verb,
  'Parses an access log entry - incorrect http verb with a trailing newline'
);

my $access_log_entry_incorrect_endpoint = '10.10.10.10 - rorymckinley%40blah.capefox.co [10/15/2018:17:47:27 -0000] ' .
  '"POST /cpsess0248462691/webmail/paper_lantern/mail/xxxx.html HTTP/1.1" 200 0 "https://cp4.capefox.co:2096/" ' .
  '"Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:61.0) Gecko/20100101 Firefox/61.0" "s" "-" 2096';
my %parsed_log_entry_incorrect_endpoint = (
  type => 'access',
  ip => '10.10.10.10',
  credential => 'rorymckinley@blah.capefox.co',
  log_time => DateTime->new(
    year => 2018,
    month => 10,
    day => 15,
    hour => 17,
    minute => 47,
    second => 27,
    time_zone => '-0000'
  )->epoch(),
  context => 'mailbox',
  event => 'unrecognised',
  status => 'success'
);

is_deeply(
  App::Spoor::AccessEntryParser::parse($access_log_entry_incorrect_endpoint),
  \%parsed_log_entry_incorrect_endpoint,
  'Parses an access log entry - incorrect endpoint'
);

is_deeply(
  App::Spoor::AccessEntryParser::parse("$access_log_entry_incorrect_endpoint\n"),
  \%parsed_log_entry_incorrect_endpoint,
  'Parses an access log entry - incorrect endpoint with a trailing newline'
);

my $access_log_entry_non_200_response = '10.10.10.10 - rorymckinley%40blah.capefox.co [10/15/2018:17:47:27 -0000] ' .
  '"POST /cpsess0248462691/webmail/paper_lantern/mail/doaddfwd.html HTTP/1.1" 400 0 "https://cp4.capefox.co:2096/" ' .
  '"Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:61.0) Gecko/20100101 Firefox/61.0" "s" "-" 2096';
my %parsed_log_entry_non_200_response = (
  type => 'access',
  ip => '10.10.10.10',
  credential => 'rorymckinley@blah.capefox.co',
  log_time => DateTime->new(

t/ApiCient.t  view on Meta::CPAN

use Test::LWP::UserAgent;
use MIME::Base64 qw(encode_base64);
use JSON;

my %config = (
  credentials => {
    api_identifier => 'user123',
    api_secret => 'secret',
  },
  host => 'http://localhost:3000',
  endpoints => {
    report => '/api/reports',
  },
  reporter => 'spoor.test.capefox.co'
);

BEGIN {
  use_ok('App::Spoor::ApiClient') || print('Could not load App::Spoor::ApiClient');
}

ok(defined(&App::Spoor::ApiClient::most_recent_reports), 'App::Spoor::ApiClient::most_recent_reports is defined');

t/Config.t  view on Meta::CPAN

      debug => 1,
      transformer => 'foo',
    }
  },
  transmission => {
    credentials => {
      api_identifier => 'user123',
      api_secret => 'secret',
    },
    host => 'https://spoor.capefox.co',
    endpoints => {
      report => '/api/reports',
      partial_report_log => '/api/partial_reports/log',
    }
  }
);

sub populate_config_file {
  my $config = YAML::Tiny->new(shift @_);
  my $root_path = shift @_;

t/EntryTransmitter.t  view on Meta::CPAN

use Test::LWP::UserAgent;
use MIME::Base64 qw(encode_base64);
use JSON;

my %config = (
  credentials => {
    api_identifier => 'user123',
    api_secret => 'secret',
  },
  host => 'http://localhost:3000',
  endpoints => {
    report => '/api/reports',
  },
  reporter => 'spoor.test.capefox.co'
);

BEGIN {
  use_ok('App::Spoor::EntryTransmitter') || print('Could not load App::Spoor::EntryTransmitter');
}

ok(defined(&App::Spoor::EntryTransmitter::transmit), 'App::Spoor::EntryTransmitter::transmit is not defined');

t/Installer.t  view on Meta::CPAN

      debug => 1,
      transformer => 'bin/login_log_transformer.pl',
    },
  },
  transmission => {
    credentials => {
      api_identifier => $installation_config{'api_identifier'},
      api_secret => $installation_config{'api_secret'},
    },
    host => 'https://spoor.capefox.co',
    endpoints => {
      report => '/api/reports',
    }
  }
);

$environment->run_test('populates config file', sub {
    App::Spoor::Installer::install(\%installation_config, $root_path);
    my $config_file = YAML::Tiny->read($config_file_path);
    my %actual_config = %{ $config_file->[0] };
    is_deeply(\%actual_config, \%expected_config, 'Configs match');

t/LoginEntryParser.t  view on Meta::CPAN

    hour => 9,
    minute => 9,
    second => 12,
    time_zone => '+0000'
  )->epoch(),
  scope => 'whostmgrd',
  ip => '10.10.10.10',
  credential => 'root',
  status => 'deferred',
  message => 'security token missing',
  endpoint => 'GET / HTTP/1.1',
  context => 'system'
);

is_deeply(
  App::Spoor::LoginEntryParser::parse($deferred_whm_login),
  \%parsed_deferred_whm_login,
	'Parses a deferred whm login');

is_deeply(
  App::Spoor::LoginEntryParser::parse("$deferred_whm_login\n"),

t/LoginEntryParser.t  view on Meta::CPAN

    hour => 15,
    minute => 42,
    second => 18,
    time_zone => '+0000'
  )->epoch(),
  scope => 'cpaneld',
  ip => '10.10.10.10',
  credential => 'cpresellercapefo',
  status => 'failed',
  message => 'access denied for root, reseller, and user password',
  endpoint => 'POST /login/?login_only=1 HTTP/1.1',
  context => 'domain'
);

is_deeply(
  App::Spoor::LoginEntryParser::parse($failed_cpaneld_login),
  \%parsed_failed_cpanel_login,
	'Parses a failed cpanel login');

is_deeply(
  App::Spoor::LoginEntryParser::parse("$failed_cpaneld_login\n"),



( run in 0.303 second using v1.01-cache-2.11-cpan-27979f6cc8f )