separate out generic device auth to DeviceAuth module
This commit is contained in:
		| @@ -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'; | ||||
|   | ||||
							
								
								
									
										149
									
								
								lib/App/Netdisco/Util/DeviceAuth.pm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								lib/App/Netdisco/Util/DeviceAuth.pm
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,149 @@ | ||||
| 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') { | ||||
|                   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); | ||||
|               } | ||||
|           } | ||||
|           else { | ||||
|             my $stanza = undef; | ||||
|             try { $stanza = from_json( $line ) }; | ||||
|             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 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user