Merge branch 'master' into og-api-tokens-simple
This commit is contained in:
		@@ -10,7 +10,8 @@ branches:
 | 
			
		||||
  only:
 | 
			
		||||
    - /^2\.\d{6}$/
 | 
			
		||||
    - 'master'
 | 
			
		||||
install: true
 | 
			
		||||
install:
 | 
			
		||||
  - cpanm --quiet --notest PkgConfig Test::CChecker Alien::zlib::Static Alien::OpenSSL::Static Alien::SNMP
 | 
			
		||||
script: |
 | 
			
		||||
  perl Build.PL && \
 | 
			
		||||
  ./Build && \
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								Build.PL
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								Build.PL
									
									
									
									
									
								
							@@ -97,6 +97,7 @@ Module::Build->new(
 | 
			
		||||
  test_requires => {
 | 
			
		||||
    'Test::More' => '1.302083',
 | 
			
		||||
    'Env::Path'  => '0',
 | 
			
		||||
    'Test::Compile' => '0',
 | 
			
		||||
    'Test::File::ShareDir::Dist' => '0',
 | 
			
		||||
  },
 | 
			
		||||
  script_files => [
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
package App::Netdisco::Configuration;
 | 
			
		||||
 | 
			
		||||
use App::Netdisco::Environment;
 | 
			
		||||
use App::Netdisco::Util::SNMP ();
 | 
			
		||||
use App::Netdisco::Util::DeviceAuth ();
 | 
			
		||||
use Dancer ':script';
 | 
			
		||||
 | 
			
		||||
use Path::Class 'dir';
 | 
			
		||||
@@ -85,7 +85,8 @@ if ((setting('snmp_auth') and 0 == scalar @{ setting('snmp_auth') })
 | 
			
		||||
  config->{'community_rw'} = [ @{setting('community_rw')}, 'private' ];
 | 
			
		||||
}
 | 
			
		||||
# fix up device_auth (or create it from old snmp_auth and community settings)
 | 
			
		||||
config->{'device_auth'} = [ App::Netdisco::Util::SNMP::fixup_device_auth() ];
 | 
			
		||||
config->{'device_auth'}
 | 
			
		||||
  = [ App::Netdisco::Util::DeviceAuth::fixup_device_auth() ];
 | 
			
		||||
 | 
			
		||||
# defaults for workers
 | 
			
		||||
setting('workers')->{queue} ||= 'PostgreSQL';
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										163
									
								
								lib/App/Netdisco/Util/DeviceAuth.pm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										163
									
								
								lib/App/Netdisco/Util/DeviceAuth.pm
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,163 @@
 | 
			
		||||
package App::Netdisco::Util::DeviceAuth;
 | 
			
		||||
 | 
			
		||||
use Dancer qw/:syntax :script/;
 | 
			
		||||
use App::Netdisco::Util::DNS 'hostname_from_ip';
 | 
			
		||||
 | 
			
		||||
use Try::Tiny;
 | 
			
		||||
 | 
			
		||||
use base 'Exporter';
 | 
			
		||||
our @EXPORT = ();
 | 
			
		||||
our @EXPORT_OK = qw/
 | 
			
		||||
  fixup_device_auth get_external_credentials
 | 
			
		||||
/;
 | 
			
		||||
our %EXPORT_TAGS = (all => \@EXPORT_OK);
 | 
			
		||||
 | 
			
		||||
=head1 NAME
 | 
			
		||||
 | 
			
		||||
App::Netdisco::Util::DeviceAuth
 | 
			
		||||
 | 
			
		||||
=head1 DESCRIPTION
 | 
			
		||||
 | 
			
		||||
Helper functions for device authentication.
 | 
			
		||||
 | 
			
		||||
There are no default exports, however the C<:all> tag will export all
 | 
			
		||||
subroutines.
 | 
			
		||||
 | 
			
		||||
=head1 EXPORT_OK
 | 
			
		||||
 | 
			
		||||
=head2 fixup_device_auth
 | 
			
		||||
 | 
			
		||||
Rebuilds the C<device_auth> config with missing defaults and other fixups for
 | 
			
		||||
config changes over time. Returns a list which can replace C<device_auth>.
 | 
			
		||||
 | 
			
		||||
=cut
 | 
			
		||||
 | 
			
		||||
sub fixup_device_auth {
 | 
			
		||||
  my $config = (setting('snmp_auth') || setting('device_auth'));
 | 
			
		||||
  my @new_stanzas = ();
 | 
			
		||||
 | 
			
		||||
  # new style snmp config
 | 
			
		||||
  foreach my $stanza (@$config) {
 | 
			
		||||
    # user tagged
 | 
			
		||||
    my $tag = '';
 | 
			
		||||
    if (1 == scalar keys %$stanza) {
 | 
			
		||||
      $tag = (keys %$stanza)[0];
 | 
			
		||||
      $stanza = $stanza->{$tag};
 | 
			
		||||
 | 
			
		||||
      # corner case: untagged lone community
 | 
			
		||||
      if ($tag eq 'community') {
 | 
			
		||||
          $tag = $stanza;
 | 
			
		||||
          $stanza = {community => $tag};
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    # defaults
 | 
			
		||||
    $stanza->{tag} ||= $tag;
 | 
			
		||||
    $stanza->{read} = 1 if !exists $stanza->{read};
 | 
			
		||||
    $stanza->{no}   ||= [];
 | 
			
		||||
    $stanza->{only} ||= ['any'];
 | 
			
		||||
 | 
			
		||||
    die "error: config: snmpv2 community in device_auth must be single item, not list\n"
 | 
			
		||||
      if ref $stanza->{community};
 | 
			
		||||
 | 
			
		||||
    die "error: config: stanza in device_auth must have a tag\n"
 | 
			
		||||
      if not $stanza->{tag} and exists $stanza->{user};
 | 
			
		||||
 | 
			
		||||
    push @new_stanzas, $stanza
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  # legacy config 
 | 
			
		||||
  # note: read strings tried before write
 | 
			
		||||
  # note: read-write is no longer used for read operations
 | 
			
		||||
 | 
			
		||||
  push @new_stanzas, map {{
 | 
			
		||||
    read => 1, write => 0,
 | 
			
		||||
    no => [], only => ['any'],
 | 
			
		||||
    community => $_,
 | 
			
		||||
  }} @{setting('community') || []};
 | 
			
		||||
 | 
			
		||||
  push @new_stanzas, map {{
 | 
			
		||||
    write => 1, read => 0,
 | 
			
		||||
    no => [], only => ['any'],
 | 
			
		||||
    community => $_,
 | 
			
		||||
  }} @{setting('community_rw') || []};
 | 
			
		||||
 | 
			
		||||
  foreach my $stanza (@new_stanzas) {
 | 
			
		||||
    $stanza->{driver} ||= 'snmp'
 | 
			
		||||
      if exists $stanza->{community}
 | 
			
		||||
         or exists $stanza->{user};
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return @new_stanzas;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
=head2 get_external_credentials( $device, $mode )
 | 
			
		||||
 | 
			
		||||
Runs a command to gather SNMP credentials or a C<device_auth> stanza.
 | 
			
		||||
 | 
			
		||||
Mode can be C<read> or C<write> and defaults to 'read'.
 | 
			
		||||
 | 
			
		||||
=cut
 | 
			
		||||
 | 
			
		||||
sub get_external_credentials {
 | 
			
		||||
  my ($device, $mode) = @_;
 | 
			
		||||
  my $cmd = (setting('get_credentials') || setting('get_community'));
 | 
			
		||||
  my $ip = $device->ip;
 | 
			
		||||
  my $host = ($device->dns || hostname_from_ip($ip) || $ip);
 | 
			
		||||
  $mode ||= 'read';
 | 
			
		||||
 | 
			
		||||
  if (defined $cmd and length $cmd) {
 | 
			
		||||
      # replace variables
 | 
			
		||||
      $cmd =~ s/\%MODE\%/$mode/egi;
 | 
			
		||||
      $cmd =~ s/\%HOST\%/$host/egi;
 | 
			
		||||
      $cmd =~ s/\%IP\%/$ip/egi;
 | 
			
		||||
 | 
			
		||||
      my $result = `$cmd`; # BACKTICKS
 | 
			
		||||
      return () unless defined $result and length $result;
 | 
			
		||||
 | 
			
		||||
      my @lines = split (m/\n/, $result);
 | 
			
		||||
      foreach my $line (@lines) {
 | 
			
		||||
          if ($line =~ m/^community\s*=\s*(.*)\s*$/i) {
 | 
			
		||||
              if (length $1 and $mode eq 'read') {
 | 
			
		||||
                  debug sprintf '[%s] external read credentials added',
 | 
			
		||||
                    $device->ip;
 | 
			
		||||
 | 
			
		||||
                  return map {{
 | 
			
		||||
                    read => 1,
 | 
			
		||||
                    only => [$device->ip],
 | 
			
		||||
                    community => $_,
 | 
			
		||||
                  }} split(m/\s*,\s*/,$1);
 | 
			
		||||
              }
 | 
			
		||||
          }
 | 
			
		||||
          elsif ($line =~ m/^setCommunity\s*=\s*(.*)\s*$/i) {
 | 
			
		||||
              if (length $1 and $mode eq 'write') {
 | 
			
		||||
                  debug sprintf '[%s] external write credentials added',
 | 
			
		||||
                    $device->ip;
 | 
			
		||||
 | 
			
		||||
                  return map {{
 | 
			
		||||
                    write => 1,
 | 
			
		||||
                    only => [$device->ip],
 | 
			
		||||
                    community => $_,
 | 
			
		||||
                  }} split(m/\s*,\s*/,$1);
 | 
			
		||||
              }
 | 
			
		||||
          }
 | 
			
		||||
          else {
 | 
			
		||||
            my $stanza = undef;
 | 
			
		||||
            try {
 | 
			
		||||
              $stanza = from_json( $line );
 | 
			
		||||
              debug sprintf '[%s] external credentials stanza added',
 | 
			
		||||
                $device->ip;
 | 
			
		||||
            }
 | 
			
		||||
            catch {
 | 
			
		||||
              info sprintf '[%s] error! failed to parse external credentials stanza',
 | 
			
		||||
                $device->ip;
 | 
			
		||||
            };
 | 
			
		||||
            return $stanza if ref $stanza;
 | 
			
		||||
          }
 | 
			
		||||
      }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return ();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
true;
 | 
			
		||||
@@ -1,14 +1,11 @@
 | 
			
		||||
package App::Netdisco::Util::SNMP;
 | 
			
		||||
 | 
			
		||||
use Dancer qw/:syntax :script/;
 | 
			
		||||
use App::Netdisco::Util::DNS 'hostname_from_ip';
 | 
			
		||||
use App::Netdisco::Util::Permission ':all';
 | 
			
		||||
use App::Netdisco::Util::DeviceAuth 'get_external_credentials';
 | 
			
		||||
 | 
			
		||||
use base 'Exporter';
 | 
			
		||||
our @EXPORT = ();
 | 
			
		||||
our @EXPORT_OK = qw/
 | 
			
		||||
  fixup_device_auth get_communities snmp_comm_reindex
 | 
			
		||||
/;
 | 
			
		||||
our @EXPORT_OK = qw/ get_communities snmp_comm_reindex /;
 | 
			
		||||
our %EXPORT_TAGS = (all => \@EXPORT_OK);
 | 
			
		||||
 | 
			
		||||
=head1 NAME
 | 
			
		||||
@@ -24,72 +21,6 @@ subroutines.
 | 
			
		||||
 | 
			
		||||
=head1 EXPORT_OK
 | 
			
		||||
 | 
			
		||||
=head2 fixup_device_auth
 | 
			
		||||
 | 
			
		||||
Rebuilds the C<device_auth> config with missing defaults and other fixups for
 | 
			
		||||
config changes over time. Returns a list which can replace C<device_auth>.
 | 
			
		||||
 | 
			
		||||
=cut
 | 
			
		||||
 | 
			
		||||
sub fixup_device_auth {
 | 
			
		||||
  my $config = (setting('snmp_auth') || setting('device_auth'));
 | 
			
		||||
  my @new_stanzas = ();
 | 
			
		||||
 | 
			
		||||
  # new style snmp config
 | 
			
		||||
  foreach my $stanza (@$config) {
 | 
			
		||||
    # user tagged
 | 
			
		||||
    my $tag = '';
 | 
			
		||||
    if (1 == scalar keys %$stanza) {
 | 
			
		||||
      $tag = (keys %$stanza)[0];
 | 
			
		||||
      $stanza = $stanza->{$tag};
 | 
			
		||||
 | 
			
		||||
      # corner case: untagged lone community
 | 
			
		||||
      if ($tag eq 'community') {
 | 
			
		||||
          $tag = $stanza;
 | 
			
		||||
          $stanza = {community => $tag};
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    # defaults
 | 
			
		||||
    $stanza->{tag} ||= $tag;
 | 
			
		||||
    $stanza->{read} = 1 if !exists $stanza->{read};
 | 
			
		||||
    $stanza->{no}   ||= [];
 | 
			
		||||
    $stanza->{only} ||= ['any'];
 | 
			
		||||
 | 
			
		||||
    die "error: config: snmpv2 community in device_auth must be single item, not list\n"
 | 
			
		||||
      if ref $stanza->{community};
 | 
			
		||||
 | 
			
		||||
    die "error: config: stanza in device_auth must have a tag\n"
 | 
			
		||||
      if not $stanza->{tag} and exists $stanza->{user};
 | 
			
		||||
 | 
			
		||||
    push @new_stanzas, $stanza
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  # legacy config 
 | 
			
		||||
  # note: read strings tried before write
 | 
			
		||||
  # note: read-write is no longer used for read operations
 | 
			
		||||
 | 
			
		||||
  push @new_stanzas, map {{
 | 
			
		||||
    read => 1, write => 0,
 | 
			
		||||
    no => [], only => ['any'],
 | 
			
		||||
    community => $_,
 | 
			
		||||
  }} @{setting('community') || []};
 | 
			
		||||
 | 
			
		||||
  push @new_stanzas, map {{
 | 
			
		||||
    write => 1, read => 0,
 | 
			
		||||
    no => [], only => ['any'],
 | 
			
		||||
    community => $_,
 | 
			
		||||
  }} @{setting('community_rw') || []};
 | 
			
		||||
 | 
			
		||||
  foreach my $stanza (@new_stanzas) {
 | 
			
		||||
    $stanza->{driver} ||= 'snmp'
 | 
			
		||||
      if exists $stanza->{community}
 | 
			
		||||
         or exists $stanza->{user};
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return @new_stanzas;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
=head2 get_communities( $device, $mode )
 | 
			
		||||
 | 
			
		||||
Takes the current C<device_auth> setting and pushes onto the front of the list
 | 
			
		||||
@@ -106,8 +37,7 @@ sub get_communities {
 | 
			
		||||
  my @communities = ();
 | 
			
		||||
 | 
			
		||||
  # first of all, use external command if configured
 | 
			
		||||
  push @communities, _get_external_community($device, $mode)
 | 
			
		||||
    if setting('get_community') and length setting('get_community');
 | 
			
		||||
  push @communities, get_external_credentials($device, $mode);
 | 
			
		||||
 | 
			
		||||
  # last known-good by tag
 | 
			
		||||
  my $tag_name = 'snmp_auth_tag_'. $mode;
 | 
			
		||||
@@ -145,46 +75,6 @@ sub get_communities {
 | 
			
		||||
  return ( @communities, @$config );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub _get_external_community {
 | 
			
		||||
  my ($device, $mode) = @_;
 | 
			
		||||
  my $cmd = setting('get_community');
 | 
			
		||||
  my $ip = $device->ip;
 | 
			
		||||
  my $host = ($device->dns || hostname_from_ip($ip) || $ip);
 | 
			
		||||
 | 
			
		||||
  if (defined $cmd and length $cmd) {
 | 
			
		||||
      # replace variables
 | 
			
		||||
      $cmd =~ s/\%HOST\%/$host/egi;
 | 
			
		||||
      $cmd =~ s/\%IP\%/$ip/egi;
 | 
			
		||||
 | 
			
		||||
      my $result = `$cmd`; # BACKTICKS
 | 
			
		||||
      return () unless defined $result and length $result;
 | 
			
		||||
 | 
			
		||||
      my @lines = split (m/\n/, $result);
 | 
			
		||||
      foreach my $line (@lines) {
 | 
			
		||||
          if ($line =~ m/^community\s*=\s*(.*)\s*$/i) {
 | 
			
		||||
              if (length $1 and $mode eq 'read') {
 | 
			
		||||
                  return map {{
 | 
			
		||||
                    read => 1,
 | 
			
		||||
                    only => [$device->ip],
 | 
			
		||||
                    community => $_,
 | 
			
		||||
                  }} split(m/\s*,\s*/,$1);
 | 
			
		||||
              }
 | 
			
		||||
          }
 | 
			
		||||
          elsif ($line =~ m/^setCommunity\s*=\s*(.*)\s*$/i) {
 | 
			
		||||
              if (length $1 and $mode eq 'write') {
 | 
			
		||||
                  return map {{
 | 
			
		||||
                    write => 1,
 | 
			
		||||
                    only => [$device->ip],
 | 
			
		||||
                    community => $_,
 | 
			
		||||
                  }} split(m/\s*,\s*/,$1);
 | 
			
		||||
              }
 | 
			
		||||
          }
 | 
			
		||||
      }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return ();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
=head2 snmp_comm_reindex( $snmp, $device, $vlan )
 | 
			
		||||
 | 
			
		||||
Takes an established L<SNMP::Info> instance and makes a fresh connection using
 | 
			
		||||
 
 | 
			
		||||
@@ -67,9 +67,13 @@ if (setting('template_paths') and ref [] eq ref setting('template_paths')) {
 | 
			
		||||
 | 
			
		||||
# load cookie key from database
 | 
			
		||||
setting('session_cookie_key' => undef);
 | 
			
		||||
my $sessions = schema('netdisco')->resultset('Session');
 | 
			
		||||
my $skey = $sessions->find({id => 'dancer_session_cookie_key'});
 | 
			
		||||
setting('session_cookie_key' => $skey->get_column('a_session')) if $skey;
 | 
			
		||||
setting('session_cookie_key' => 'this_is_for_testing_only')
 | 
			
		||||
  if $ENV{HARNESS_ACTIVE};
 | 
			
		||||
eval {
 | 
			
		||||
  my $sessions = schema('netdisco')->resultset('Session');
 | 
			
		||||
  my $skey = $sessions->find({id => 'dancer_session_cookie_key'});
 | 
			
		||||
  setting('session_cookie_key' => $skey->get_column('a_session')) if $skey;
 | 
			
		||||
};
 | 
			
		||||
Dancer::Session::Cookie::init(session);
 | 
			
		||||
 | 
			
		||||
# setup for swagger API
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,8 @@ register_worker({ phase => 'main' }, sub {
 | 
			
		||||
  my $extra = $job->extra;
 | 
			
		||||
 | 
			
		||||
  my $config = config();
 | 
			
		||||
  p ($extra ? $config->{$extra} : $config);
 | 
			
		||||
  my $dump = ($extra ? $config->{$extra} : $config);
 | 
			
		||||
  p $dump;
 | 
			
		||||
  return Status->done('Dumped config');
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -217,7 +217,7 @@ device_identity: []
 | 
			
		||||
community: []
 | 
			
		||||
community_rw: []
 | 
			
		||||
device_auth: []
 | 
			
		||||
get_community: ""
 | 
			
		||||
get_credentials: ""
 | 
			
		||||
bulkwalk_off: false
 | 
			
		||||
bulkwalk_no: []
 | 
			
		||||
bulkwalk_repeaters: 20
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										30
									
								
								xt/00-compile.t
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								xt/00-compile.t
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
#!/usr/bin/env perl
 | 
			
		||||
 | 
			
		||||
use strict;
 | 
			
		||||
use warnings;
 | 
			
		||||
 | 
			
		||||
BEGIN {
 | 
			
		||||
  use FindBin;
 | 
			
		||||
  FindBin::again();
 | 
			
		||||
 | 
			
		||||
  use Path::Class;
 | 
			
		||||
 | 
			
		||||
  # stuff useful locations into @INC and $PATH
 | 
			
		||||
  unshift @INC,
 | 
			
		||||
    dir($FindBin::RealBin)->parent->subdir('lib')->stringify,
 | 
			
		||||
    dir($FindBin::RealBin, 'lib')->stringify;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# for netdisco app config
 | 
			
		||||
use App::Netdisco;
 | 
			
		||||
use Test::Compile;
 | 
			
		||||
 | 
			
		||||
my $test = Test::Compile->new();
 | 
			
		||||
 | 
			
		||||
my @plfiles = grep {$_ !~ m/(?:sshcollector|graph)/i} $test->all_pl_files();
 | 
			
		||||
my @pmfiles = grep {$_ !~ m/(?:sshcollector|graph)/i} $test->all_pm_files();
 | 
			
		||||
 | 
			
		||||
$test->ok($test->pl_file_compiles($_), "$_ compiles") for @plfiles;
 | 
			
		||||
$test->ok($test->pm_file_compiles($_), "$_ compiles") for @pmfiles;
 | 
			
		||||
 | 
			
		||||
$test->done_testing();
 | 
			
		||||
		Reference in New Issue
	
	Block a user