diff --git a/lib/App/Netdisco/Configuration.pm b/lib/App/Netdisco/Configuration.pm index 90cdc5ac..9ddd1d0f 100644 --- a/lib/App/Netdisco/Configuration.pm +++ b/lib/App/Netdisco/Configuration.pm @@ -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'; @@ -84,7 +84,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'; diff --git a/lib/App/Netdisco/Util/DeviceAuth.pm b/lib/App/Netdisco/Util/DeviceAuth.pm new file mode 100644 index 00000000..bb7f79f5 --- /dev/null +++ b/lib/App/Netdisco/Util/DeviceAuth.pm @@ -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 config with missing defaults and other fixups for +config changes over time. Returns a list which can replace C. + +=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 stanza. + +Mode can be C or C 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; diff --git a/lib/App/Netdisco/Util/SNMP.pm b/lib/App/Netdisco/Util/SNMP.pm index 3fa6c0a6..ba5b54d6 100644 --- a/lib/App/Netdisco/Util/SNMP.pm +++ b/lib/App/Netdisco/Util/SNMP.pm @@ -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 config with missing defaults and other fixups for -config changes over time. Returns a list which can replace C. - -=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 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 instance and makes a fresh connection using diff --git a/share/config.yml b/share/config.yml index 24b31d66..9767019a 100644 --- a/share/config.yml +++ b/share/config.yml @@ -216,7 +216,7 @@ device_identity: [] community: [] community_rw: [] device_auth: [] -get_community: "" +get_credentials: "" bulkwalk_off: false bulkwalk_no: [] bulkwalk_repeaters: 20