Refactored ACL support with multi-object compare

Squashed commit of the following:

commit 4081e22202693bd7c4ea00e95daad8e628c6fd5a
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Mon May 29 21:02:07 2023 +0100

    large rename of check_acl* to acl_matches*

commit 3cfa284ddd24d68765c255578cc5c184afbdcd83
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Fri May 19 20:39:03 2023 +0100

    update permission doc

commit 8c7bb93cc5e9fafb770f98f446e45cbd94b14894
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Wed May 17 21:50:07 2023 +0100

    migrate most check_acl_only to acl_matches_only

commit c47f699f2a22f08f2f3e093ed0f24c891e6f9a82
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Wed May 17 21:39:19 2023 +0100

    rename check_acl* to be acl_matches*

commit a884a22c3ab1f3262118c3a47ed8e25b0b0a7336
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Sun May 14 16:50:42 2023 +0100

    update macsuck_no_deviceports to use acl_matches

commit 8c256af728721329b64d071fa529dfc844073ac6
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Sun May 7 22:54:33 2023 +0100

    update hide_deviceports to use acl_matches multi @things

commit cd5d9978aba1da459be4fed4500f395df13f7784
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Sun May 7 22:53:38 2023 +0100

    check_acl fix to allow all @things to offer a property before fallback to missing as empty string

commit 1a3ab9a7646e9f994f03126d45fc36e9e5a13ed5
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Tue May 2 15:31:17 2023 +0100

    add ignore_deviceports to portproperties discover; improve comments

commit 51385ce89458dc939587dae902fda431719c22c9
Merge: b97c07d2 3f8ffe78
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Tue May 2 15:21:48 2023 +0100

    Merge branch 'master' into og-acl_multidict

commit b97c07d237d750c1d9eb3095d8ff3908512eac2a
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Sat Mar 25 14:37:53 2023 +0000

    add support for arrayref of items, and unblessed hash, to check_acl
This commit is contained in:
Oliver Gorwits
2023-05-29 21:32:07 +01:00
parent 3f8ffe787f
commit 9355f5c2b9
27 changed files with 463 additions and 335 deletions

View File

@@ -90,7 +90,6 @@ Module::Build->new(
'SNMP::Info' => '3.92', 'SNMP::Info' => '3.92',
'SQL::Abstract' => '1.85', 'SQL::Abstract' => '1.85',
'SQL::Translator' => '0.11024', 'SQL::Translator' => '0.11024',
'Sub::Install' => '0',
'Sub::Util' => '1.40', 'Sub::Util' => '1.40',
'Template' => '2.24', 'Template' => '2.24',
'Template::AutoFilter' => '0', 'Template::AutoFilter' => '0',

View File

@@ -40,7 +40,7 @@ use App::Netdisco;
use Dancer ':script'; use Dancer ':script';
use Dancer::Plugin::DBIC 'schema'; use Dancer::Plugin::DBIC 'schema';
use App::Netdisco::Util::Permission ':all'; use App::Netdisco::Util::Permission 'acl_matches';
# silent exit unless explicitly requested # silent exit unless explicitly requested
exit(0) unless setting('use_legacy_rancidexport'); exit(0) unless setting('use_legacy_rancidexport');
@@ -76,7 +76,7 @@ foreach my $d (@devices) {
my $old = $d->get_column( 'old' ); my $old = $d->get_column( 'old' );
my $devgroup = 'other'; my $devgroup = 'other';
foreach my $g (keys %$groups) { foreach my $g (keys %$groups) {
if (check_acl_only( $d, $groups->{$g} )) { if (acl_matches( $d, $groups->{$g} )) {
$devgroup = $g; $devgroup = $g;
last; last;
} }

View File

@@ -6,7 +6,6 @@ use strict;
use warnings; use warnings;
use base 'App::Netdisco::DB::Result'; use base 'App::Netdisco::DB::Result';
use Sub::Install;
__PACKAGE__->table("device_ip"); __PACKAGE__->table("device_ip");
__PACKAGE__->add_columns( __PACKAGE__->add_columns(
@@ -52,38 +51,4 @@ routed port or virtual interface).
__PACKAGE__->belongs_to( device_port => 'App::Netdisco::DB::Result::DevicePort', __PACKAGE__->belongs_to( device_port => 'App::Netdisco::DB::Result::DevicePort',
{ 'foreign.port' => 'self.port', 'foreign.ip' => 'self.ip' } ); { 'foreign.port' => 'self.port', 'foreign.ip' => 'self.ip' } );
=head2 device_port fields
All C<device_port> fields are mapped to accessors on this object.
=cut
foreach my $field (qw/
descr
up
up_admin
type
duplex
duplex_admin
speed
speed_admin
name
mac
mtu
stp
remote_ip
remote_port
remote_type
remote_id
vlan
pvid
lastchange
/) {
Sub::Install::install_sub({
code => sub { return eval { (shift)->device_port->$field } },
as => $field,
});
}
1; 1;

View File

@@ -5,7 +5,7 @@ use Dancer::Plugin::DBIC 'schema';
use App::Netdisco::Util::SNMP 'get_communities'; use App::Netdisco::Util::SNMP 'get_communities';
use App::Netdisco::Util::Device 'get_device'; use App::Netdisco::Util::Device 'get_device';
use App::Netdisco::Util::Permission ':all'; use App::Netdisco::Util::Permission 'acl_matches';
use SNMP::Info; use SNMP::Info;
use Try::Tiny; use Try::Tiny;
@@ -97,7 +97,7 @@ sub test_connection {
# avoid renumbering to localhost loopbacks # avoid renumbering to localhost loopbacks
return undef if $addr->addr eq '0.0.0.0' return undef if $addr->addr eq '0.0.0.0'
or check_acl_no($addr->addr, 'group:__LOOPBACK_ADDRESSES__'); or acl_matches($addr->addr, 'group:__LOOPBACK_ADDRESSES__');
my $device = schema(vars->{'tenant'})->resultset('Device') my $device = schema(vars->{'tenant'})->resultset('Device')
->new_result({ ip => $addr->addr }) or return undef; ->new_result({ ip => $addr->addr }) or return undef;
@@ -154,11 +154,11 @@ sub _snmp_connect_generic {
# an override for RemotePort # an override for RemotePort
($snmp_args{RemotePort}) = ($snmp_args{RemotePort}) =
(pairkeys pairfirst { check_acl_no($device, $b) } (pairkeys pairfirst { acl_matches($device, $b) }
%{setting('snmp_remoteport') || {}}) || 161; %{setting('snmp_remoteport') || {}}) || 161;
# an override for bulkwalk # an override for bulkwalk
$snmp_args{BulkWalk} = 0 if check_acl_no($device, 'bulkwalk_no'); $snmp_args{BulkWalk} = 0 if acl_matches($device, 'bulkwalk_no');
# further protect against buggy Net-SNMP, and disable bulkwalk # further protect against buggy Net-SNMP, and disable bulkwalk
if ($snmp_args{BulkWalk} if ($snmp_args{BulkWalk}
@@ -197,9 +197,9 @@ sub _snmp_connect_generic {
# which SNMP versions to try and in what order # which SNMP versions to try and in what order
my @versions = my @versions =
( check_acl_no($device->ip, 'snmpforce_v3') ? (3) ( acl_matches($device->ip, 'snmpforce_v3') ? (3)
: check_acl_no($device->ip, 'snmpforce_v2') ? (2) : acl_matches($device->ip, 'snmpforce_v2') ? (2)
: check_acl_no($device->ip, 'snmpforce_v1') ? (1) : acl_matches($device->ip, 'snmpforce_v1') ? (1)
: (reverse (1 .. (setting('snmpver') || 3))) ); : (reverse (1 .. (setting('snmpver') || 3))) );
# use existing or new device class # use existing or new device class

View File

@@ -2,7 +2,7 @@ package App::Netdisco::Util::Device;
use Dancer qw/:syntax :script/; use Dancer qw/:syntax :script/;
use Dancer::Plugin::DBIC 'schema'; use Dancer::Plugin::DBIC 'schema';
use App::Netdisco::Util::Permission qw/check_acl_no check_acl_only/; use App::Netdisco::Util::Permission qw/acl_matches acl_matches_only/;
use File::Spec::Functions qw(catdir catfile); use File::Spec::Functions qw(catdir catfile);
use File::Path 'make_path'; use File::Path 'make_path';
@@ -184,10 +184,10 @@ sub is_discoverable {
if (match_to_setting($remote_type, 'discover_no_type')); if (match_to_setting($remote_type, 'discover_no_type'));
return _bail_msg("is_discoverable: $device matched discover_no") return _bail_msg("is_discoverable: $device matched discover_no")
if check_acl_no($device, 'discover_no'); if acl_matches($device, 'discover_no');
return _bail_msg("is_discoverable: $device failed to match discover_only") return _bail_msg("is_discoverable: $device failed to match discover_only")
unless check_acl_only($device, 'discover_only'); unless acl_matches_only($device, 'discover_only');
return 1; return 1;
} }
@@ -236,14 +236,14 @@ sub is_arpnipable {
return _bail_msg("is_arpnipable: $device has no layer 3 capability") return _bail_msg("is_arpnipable: $device has no layer 3 capability")
if ($device->in_storage() and not ($device->has_layer(3) if ($device->in_storage() and not ($device->has_layer(3)
or check_acl_no($device, 'force_arpnip') or acl_matches($device, 'force_arpnip')
or check_acl_no($device, 'ignore_layers'))); or acl_matches($device, 'ignore_layers')));
return _bail_msg("is_arpnipable: $device matched arpnip_no") return _bail_msg("is_arpnipable: $device matched arpnip_no")
if check_acl_no($device, 'arpnip_no'); if acl_matches($device, 'arpnip_no');
return _bail_msg("is_arpnipable: $device failed to match arpnip_only") return _bail_msg("is_arpnipable: $device failed to match arpnip_only")
unless check_acl_only($device, 'arpnip_only'); unless acl_matches_only($device, 'arpnip_only');
return 1; return 1;
} }
@@ -292,17 +292,17 @@ sub is_macsuckable {
return _bail_msg("is_macsuckable: $device has no layer 2 capability") return _bail_msg("is_macsuckable: $device has no layer 2 capability")
if ($device->in_storage() and not ($device->has_layer(2) if ($device->in_storage() and not ($device->has_layer(2)
or check_acl_no($device, 'force_macsuck') or acl_matches($device, 'force_macsuck')
or check_acl_no($device, 'ignore_layers'))); or acl_matches($device, 'ignore_layers')));
return _bail_msg("is_macsuckable: $device matched macsuck_no") return _bail_msg("is_macsuckable: $device matched macsuck_no")
if check_acl_no($device, 'macsuck_no'); if acl_matches($device, 'macsuck_no');
return _bail_msg("is_macsuckable: $device matched macsuck_unsupported") return _bail_msg("is_macsuckable: $device matched macsuck_unsupported")
if check_acl_no($device, 'macsuck_unsupported'); if acl_matches($device, 'macsuck_unsupported');
return _bail_msg("is_macsuckable: $device failed to match macsuck_only") return _bail_msg("is_macsuckable: $device failed to match macsuck_only")
unless check_acl_only($device, 'macsuck_only'); unless acl_matches_only($device, 'macsuck_only');
return 1; return 1;
} }

View File

@@ -5,7 +5,7 @@ use warnings;
use Dancer ':script'; use Dancer ':script';
use AnyEvent::DNS; use AnyEvent::DNS;
use App::Netdisco::Util::Permission 'check_acl_no'; use App::Netdisco::Util::Permission 'acl_matches';
use base 'Exporter'; use base 'Exporter';
our @EXPORT = (); our @EXPORT = ();
@@ -58,7 +58,7 @@ sub hostnames_resolve_async {
IP: foreach my $hash_ref (@$ips) { IP: foreach my $hash_ref (@$ips) {
my $ip = $hash_ref->{'ip'} || $hash_ref->{'alias'} || $hash_ref->{'device'}; my $ip = $hash_ref->{'ip'} || $hash_ref->{'alias'} || $hash_ref->{'device'};
next IP if check_acl_no($ip, $skip); next IP if acl_matches($ip, $skip);
# check /etc/hosts file and short-circuit if found # check /etc/hosts file and short-circuit if found
foreach my $name (reverse sort keys %$ETCHOSTS) { foreach my $name (reverse sort keys %$ETCHOSTS) {

View File

@@ -4,7 +4,7 @@ use Dancer qw/:syntax :script/;
use Dancer::Plugin::DBIC 'schema'; use Dancer::Plugin::DBIC 'schema';
use NetAddr::MAC; use NetAddr::MAC;
use App::Netdisco::Util::Permission qw/check_acl_no check_acl_only/; use App::Netdisco::Util::Permission qw/acl_matches acl_matches_only/;
use base 'Exporter'; use base 'Exporter';
our @EXPORT = (); our @EXPORT = ();
@@ -138,9 +138,9 @@ Returns false if the host is not permitted to nbtstat the target node.
sub is_nbtstatable { sub is_nbtstatable {
my $ip = shift; my $ip = shift;
return if check_acl_no($ip, 'nbtstat_no'); return if acl_matches($ip, 'nbtstat_no');
return unless check_acl_only($ip, 'nbtstat_only'); return unless acl_matches_only($ip, 'nbtstat_only');
return 1; return 1;
} }

View File

@@ -10,7 +10,7 @@ use App::Netdisco::Util::DNS 'hostname_from_ip';
use base 'Exporter'; use base 'Exporter';
our @EXPORT = (); our @EXPORT = ();
our @EXPORT_OK = qw/check_acl check_acl_no check_acl_only/; our @EXPORT_OK = qw/acl_matches acl_matches_only/;
our %EXPORT_TAGS = (all => \@EXPORT_OK); our %EXPORT_TAGS = (all => \@EXPORT_OK);
=head1 NAME =head1 NAME
@@ -26,11 +26,17 @@ subroutines.
=head1 EXPORT_OK =head1 EXPORT_OK
=head2 check_acl_no( $ip | $instance, $setting_name | $acl_entry | \@acl ) =head2 acl_matches( $ip | $object | \%hash | \@item_list, $setting_name | $acl_entry | \@acl )
Given an IP address or object instance, returns true if the configuration Given an IP address, object instance, or hash, returns true if the
setting C<$setting_name> matches, else returns false. If the content of the configuration setting C<$setting_name> matches, else returns false.
setting is undefined or empty, then C<check_acl_no> also returns false.
Usage of this function is strongly advised to be of the form:
QUIT/SKIP IF acl_matches
The function fails safe, so if the content of the setting or ACL is undefined
or an empty string, then C<acl_matches> also returns true.
If C<$setting_name> is a valid setting, then it will be resolved to the access If C<$setting_name> is a valid setting, then it will be resolved to the access
control list, else we assume you passed an ACL entry or ACL. control list, else we assume you passed an ACL entry or ACL.
@@ -40,19 +46,37 @@ for details of what C<$acl> may contain.
=cut =cut
sub check_acl_no { sub acl_matches {
my ($thing, $setting_name) = @_; my ($thing, $setting_name) = @_;
return 1 unless $thing and $setting_name; # fail-safe so undef config should return true
return true unless $thing and $setting_name;
my $config = (exists config->{"$setting_name"} ? setting($setting_name) my $config = (exists config->{"$setting_name"} ? setting($setting_name)
: $setting_name); : $setting_name);
return check_acl($thing, $config); return check_acl($thing, $config);
} }
=head2 check_acl_only( $ip | $instance, $setting_name | $acl_entry | \@acl ) =head2 check_acl_no( $ip | $object | \%hash | \@item_list, $setting_name | $acl_entry | \@acl )
Given an IP address or object instance, returns true if the configuration This is an alias for L<acl_matches>.
setting C<$setting_name> matches, else returns false. If the content of the
setting is undefined or empty, then C<check_acl_only> also returns true. =cut
sub check_acl_no { goto &acl_matches }
=head2 acl_matches_only( $ip | $object | \%hash | \@item_list, $setting_name | $acl_entry | \@acl )
Given an IP address, object instance, or hash, returns true if the
configuration setting C<$setting_name> matches, else returns false.
Usage of this function is strongly advised to be of the form:
QUIT/SKIP UNLESS acl_matches_only
The function fails safe, so if the content of the setting or ACL is undefined
or an empty string, then C<acl_matches_only> also returns false.
Further, if the setting or ACL resolves to a list but the list has no items,
then C<acl_matches_only> returns true (as if there is a successful match).
If C<$setting_name> is a valid setting, then it will be resolved to the access If C<$setting_name> is a valid setting, then it will be resolved to the access
control list, else we assume you passed an ACL entry or ACL. control list, else we assume you passed an ACL entry or ACL.
@@ -62,25 +86,40 @@ for details of what C<$acl> may contain.
=cut =cut
sub check_acl_only { sub acl_matches_only {
my ($thing, $setting_name) = @_; my ($thing, $setting_name) = @_;
return 0 unless $thing and $setting_name; # fail-safe so undef config should return false
# logic to make an empty config be equivalent to 'any' (i.e. a match) return false unless $thing and $setting_name;
my $config = (exists config->{"$setting_name"} ? setting($setting_name) my $config = (exists config->{"$setting_name"} ? setting($setting_name)
: $setting_name); : $setting_name);
return 1 if not $config # undef or empty string # logic to make an empty config be equivalent to 'any' (i.e. a match)
# empty list check means truth check passes for match or empty list
return true if not $config # undef or empty string
or ((ref [] eq ref $config) and not scalar @$config); or ((ref [] eq ref $config) and not scalar @$config);
return check_acl($thing, $config); return check_acl($thing, $config);
} }
=head2 check_acl( $ip | $instance, $acl_entry | \@acl ) =head2 check_acl_only( $ip | $object | \%hash | \@item_list, $setting_name | $acl_entry | \@acl )
Given an IP address or object instance, compares it to the items in C<< \@acl This is an alias for L<acl_matches_only>.
>> then returns true or false. You can control whether any item must match or
all must match, and items can be negated to invert the match logic.
Accepts instances of classes representing Netdisco Devices, Netdisco Device =cut
IPs, and L<NetAddr::IP> family objects.
sub check_acl_only { goto &acl_matches_only }
=head2 check_acl( $ip | $object | \%hash | \@item_list, $acl_entry | \@acl )
Given an IP address, object instance, or hash, compares it to the items in
C<< \@acl >> then returns true or false. You can control whether any item must
match or all must match, and items can be negated to invert the match logic.
Also accepts an array reference of multiple IP addresses, object instances,
and hashes, and will test against each in turn, for each ACL rule.
The slots C<alias>, C<ip>, C<switch>, and C<addr> are looked for in the
instance or hash and used to compare a bare IP address (so it works with most
Netdisco database classes, and the L<NetAddr::IP> class). Any instance or hash
slot can be used as an ACL named property.
There are several options for what C<< \@acl >> may contain. See There are several options for what C<< \@acl >> may contain. See
L<the Netdisco wiki|https://github.com/netdisco/netdisco/wiki/Configuration#access-control-lists> L<the Netdisco wiki|https://github.com/netdisco/netdisco/wiki/Configuration#access-control-lists>
@@ -89,156 +128,199 @@ for the details.
=cut =cut
sub check_acl { sub check_acl {
my ($thing, $config) = @_; my ($things, $config) = @_;
return 0 unless defined $thing and defined $config; return false unless defined $things and defined $config;
return false if ref [] eq ref $things and not scalar @$things;
$things = [$things] if ref [] ne ref $things;
my $real_ip = $thing; my $real_ip = ''; # valid to be empty
if (blessed $thing) { ITEM: foreach my $item (@$things) {
$real_ip = ( foreach my $slot (qw/alias ip switch addr/) {
$thing->can('alias') ? $thing->alias : ( if (blessed $item) {
$thing->can('ip') ? $thing->ip : ( $real_ip = $item->$slot if $item->can($slot)
$thing->can('addr') ? $thing->addr : $thing ))); and eval { $item->$slot };
}
elsif (ref {} eq ref $item) {
$real_ip = $item->{$slot} if exists $item->{$slot}
and $item->{$slot};
}
last ITEM if $real_ip;
}
}
ITEM: foreach my $item (@$things) {
last ITEM if $real_ip;
$real_ip = $item if (ref $item eq q{}) and $item;
} }
return 0 if blessed $real_ip; # class we do not understand
$real_ip ||= ''; # valid to be empty
$config = [$config] if ref q{} eq ref $config; $config = [$config] if ref $config eq q{};
if (ref [] ne ref $config) { if (ref [] ne ref $config) {
error "error: acl is not a single item or list (cannot compare to '$real_ip')"; error "error: acl is not a single item or list (cannot compare to '$real_ip')";
return 0; return false;
} }
my $all = (scalar grep {$_ eq 'op:and'} @$config); my $all = (scalar grep {$_ eq 'op:and'} @$config);
# common case of using plain IP in ACL, so string compare for speed # common case of using plain IP in ACL, so string compare for speed
my $find = (scalar grep {not reftype $_ and $_ eq $real_ip} @$config); my $find = (scalar grep {not reftype $_ and $_ eq $real_ip} @$config);
return 1 if $real_ip and $find and not $all; return true if $real_ip and $find and not $all;
my $addr = NetAddr::IP::Lite->new($real_ip); my $addr = NetAddr::IP::Lite->new($real_ip);
my $name = undef; # only look up once, and only if qr// is used my $name = undef; # only look up once, and only if qr// is used
my $ropt = { retry => 1, retrans => 1, udp_timeout => 1, tcp_timeout => 2 }; my $ropt = { retry => 1, retrans => 1, udp_timeout => 1, tcp_timeout => 2 };
my $qref = ref qr//; my $qref = ref qr//;
INLIST: foreach (@$config) { RULE: foreach (@$config) {
my $item = $_; # must copy so that we can modify safely my $rule = $_; # must copy so that we can modify safely
next INLIST if !defined $item or $item eq 'op:and'; next RULE if !defined $rule or $rule eq 'op:and';
if ($qref eq ref $item) { if ($qref eq ref $rule) {
# if no IP addr, cannot match its dns # if no IP addr, cannot match its dns
next INLIST unless $addr; next RULE unless $addr;
$name = ($name || hostname_from_ip($addr->addr, $ropt) || '!!none!!'); $name = ($name || hostname_from_ip($addr->addr, $ropt) || '!!none!!');
if ($name =~ $item) { if ($name =~ $rule) {
return 1 if not $all; return true if not $all;
} }
else { else {
return 0 if $all; return false if $all;
} }
next INLIST; next RULE;
} }
my $neg = ($item =~ s/^!//); my $neg = ($rule =~ s/^!//);
if ($item =~ m/^group:(.+)$/) { if ($rule =~ m/^group:(.+)$/) {
my $group = $1; my $group = $1;
setting('host_groups')->{$group} ||= []; setting('host_groups')->{$group} ||= [];
if ($neg xor check_acl($thing, setting('host_groups')->{$group})) { if ($neg xor check_acl($things, setting('host_groups')->{$group})) {
return 1 if not $all; return true if not $all;
} }
else { else {
return 0 if $all; return false if $all;
} }
next INLIST; next RULE;
} }
if ($item =~ m/^([^:]+):([^:]*)$/) {
my $prop = $1;
my $match = $2 || '';
# if not an object, we can't do much with properties
next INLIST unless blessed $thing;
# prop:val # prop:val
if ($neg xor ($thing->can($prop) and if ($rule =~ m/^([^:]+):([^:]*)$/) {
defined eval { $thing->$prop } and my $prop = $1;
ref $thing->$prop eq q{} my $match = $2 || '';
and $thing->$prop =~ m/^$match$/) ) { my $found = false;
return 1 if not $all;
# property exists, undef is allowed to match empty string
ITEM: foreach my $item (@$things) {
if (blessed $item) {
if ($neg xor ($item->can($prop) and
((!defined eval { $item->$prop } and $match eq q{})
or
(defined eval { $item->$prop } and ref $item->$prop eq q{} and $item->$prop =~ m/^$match$/)) )) {
return true if not $all;
$found = true;
last ITEM;
} }
# empty or missing property
elsif ($neg xor ($match eq q{} and
(!defined eval { $thing->$prop } or $thing->$prop eq q{})) ) {
return 1 if not $all;
} }
else { elsif (ref {} eq ref $item) {
return 0 if $all; if ($neg xor (exists $item->{$prop} and
((!defined $item->{$prop} and $match eq q{})
or
(defined $item->{$prop} and ref $item->{$prop} eq q{} and $item->{$prop} =~ m/^$match$/)) )) {
return true if not $all;
$found = true;
last ITEM;
}
} }
next INLIST;
} }
if ($item =~ m/[:.]([a-f0-9]+)-([a-f0-9]+)$/i) { # missing property matches empty string
# (which is done in a second pass to allow all @$things to be
# inspected for existing properties)
ITEM: foreach my $item (@$things) {
last ITEM if $found;
if (blessed $item) {
if ($neg xor ($match eq q{} and ! $item->can($prop))) {
return true if not $all;
$found = true;
last ITEM;
}
}
elsif (ref {} eq ref $item) {
# empty or missing property
if ($neg xor ($match eq q{} and ! exists $item->{$prop})) {
return true if not $all;
$found = true;
last ITEM;
}
}
}
return false if $all;
next RULE;
}
if ($rule =~ m/[:.]([a-f0-9]+)-([a-f0-9]+)$/i) {
my $first = $1; my $first = $1;
my $last = $2; my $last = $2;
# if no IP addr, cannot match IP range # if no IP addr, cannot match IP range
next INLIST unless $addr; next RULE unless $addr;
if ($item =~ m/:/) { if ($rule =~ m/:/) {
next INLIST if $addr->bits != 128 and not $all; next RULE if $addr->bits != 128 and not $all;
$first = hex $first; $first = hex $first;
$last = hex $last; $last = hex $last;
(my $header = $item) =~ s/:[^:]+$/:/; (my $header = $rule) =~ s/:[^:]+$/:/;
foreach my $part ($first .. $last) { foreach my $part ($first .. $last) {
my $ip = NetAddr::IP::Lite->new($header . sprintf('%x',$part) . '/128') my $ip = NetAddr::IP::Lite->new($header . sprintf('%x',$part) . '/128')
or next; or next;
if ($neg xor ($ip == $addr)) { if ($neg xor ($ip == $addr)) {
return 1 if not $all; return true if not $all;
next INLIST; next RULE;
} }
} }
return 0 if (not $neg and $all); return false if (not $neg and $all);
return 1 if ($neg and not $all); return true if ($neg and not $all);
} }
else { else {
next INLIST if $addr->bits != 32 and not $all; next RULE if $addr->bits != 32 and not $all;
(my $header = $item) =~ s/\.[^.]+$/./; (my $header = $rule) =~ s/\.[^.]+$/./;
foreach my $part ($first .. $last) { foreach my $part ($first .. $last) {
my $ip = NetAddr::IP::Lite->new($header . $part . '/32') my $ip = NetAddr::IP::Lite->new($header . $part . '/32')
or next; or next;
if ($neg xor ($ip == $addr)) { if ($neg xor ($ip == $addr)) {
return 1 if not $all; return true if not $all;
next INLIST; next RULE;
} }
} }
return 0 if (not $neg and $all); return false if (not $neg and $all);
return 1 if ($neg and not $all); return true if ($neg and not $all);
} }
next INLIST; next RULE;
} }
# could be something in error, and IP/host is only option left # could be something in error, and IP/host is only option left
next INLIST if ref $item; next RULE if ref $rule;
# if no IP addr, cannot match IP prefix # if no IP addr, cannot match IP prefix
next INLIST unless $addr; next RULE unless $addr;
my $ip = NetAddr::IP::Lite->new($item) my $ip = NetAddr::IP::Lite->new($rule)
or next INLIST; or next RULE;
next INLIST if $ip->bits != $addr->bits and not $all; next RULE if $ip->bits != $addr->bits and not $all;
if ($neg xor ($ip->contains($addr))) { if ($neg xor ($ip->contains($addr))) {
return 1 if not $all; return true if not $all;
} }
else { else {
return 0 if $all; return false if $all;
} }
next INLIST; next RULE;
} }
return ($all ? 1 : 0); return ($all ? true : false);
} }
1; true;

View File

@@ -4,7 +4,7 @@ use Dancer qw/:syntax :script/;
use Dancer::Plugin::DBIC 'schema'; use Dancer::Plugin::DBIC 'schema';
use App::Netdisco::Util::Device 'get_device'; use App::Netdisco::Util::Device 'get_device';
use App::Netdisco::Util::Permission qw/check_acl_no check_acl_only/; use App::Netdisco::Util::Permission qw/acl_matches acl_matches_only/;
use base 'Exporter'; use base 'Exporter';
our @EXPORT = (); our @EXPORT = ();
@@ -107,9 +107,9 @@ sub port_reconfig_check {
# check for limits on devices # check for limits on devices
return "forbidden: device [$ip] is in denied ACL" return "forbidden: device [$ip] is in denied ACL"
if check_acl_no($ip, 'portctl_no'); if acl_matches($ip, 'portctl_no');
return "forbidden: device [$ip] is not in permitted ACL" return "forbidden: device [$ip] is not in permitted ACL"
unless check_acl_only($ip, 'portctl_only'); unless acl_matches_only($ip, 'portctl_only');
# only permitted to change interface name # only permitted to change interface name
return "forbidden: not permitted to change port configuration" return "forbidden: not permitted to change port configuration"

View File

@@ -7,7 +7,7 @@ use Dancer::Plugin::Auth::Extensible;
use List::Util 'first'; use List::Util 'first';
use List::MoreUtils (); use List::MoreUtils ();
use App::Netdisco::Util::Permission 'check_acl_only'; use App::Netdisco::Util::Permission 'acl_matches';
use App::Netdisco::Web::Plugin; use App::Netdisco::Web::Plugin;
register_device_tab({ tag => 'netmap', label => 'Neighbors' }); register_device_tab({ tag => 'netmap', label => 'Neighbors' });
@@ -228,11 +228,11 @@ ajax '/ajax/data/device/netmap' => require_login sub {
# if host groups picked then use ACLs to filter # if host groups picked then use ACLs to filter
my $first_hgrp = my $first_hgrp =
first { check_acl_only($device, setting('host_groups')->{$_}) } @hgrplist; first { acl_matches($device, setting('host_groups')->{$_}) } @hgrplist;
next DEVICE if ((scalar @hgrplist) and (not $first_hgrp)); next DEVICE if ((scalar @hgrplist) and (not $first_hgrp));
# now reset first_hgroup to be the group matching the device, if any # now reset first_hgroup to be the group matching the device, if any
$first_hgrp = first { check_acl_only($device, setting('host_groups')->{$_}) } $first_hgrp = first { acl_matches($device, setting('host_groups')->{$_}) }
keys %{ setting('host_group_displaynames') || {} }; keys %{ setting('host_group_displaynames') || {} };
++$logvals{ $device->get_column('log') || 1 }; ++$logvals{ $device->get_column('log') || 1 };

View File

@@ -4,7 +4,7 @@ use Dancer ':syntax';
use Dancer::Plugin::DBIC; use Dancer::Plugin::DBIC;
use Dancer::Plugin::Auth::Extensible; use Dancer::Plugin::Auth::Extensible;
use App::Netdisco::Util::Permission 'check_acl_no'; use App::Netdisco::Util::Permission 'acl_matches';
use App::Netdisco::Util::Port 'port_reconfig_check'; use App::Netdisco::Util::Port 'port_reconfig_check';
use App::Netdisco::Util::Web (); # for sort_port use App::Netdisco::Util::Web (); # for sort_port
use App::Netdisco::Web::Plugin; use App::Netdisco::Web::Plugin;
@@ -217,31 +217,33 @@ get '/ajax/content/device/ports' => require_login sub {
# filter out hidden ones # filter out hidden ones
if (not param('p_include_hidden')) { if (not param('p_include_hidden')) {
my $device_ips = {}; my $port_map = {};
map { push @{ $device_ips->{$_->port} }, $_ } my %to_hide = ();
$device->device_ips(undef, {prefetch => 'device_port'})->all;
map { push @{ $device_ips->{$_->port} }, $_ } map { push @{ $port_map->{$_->port} }, $_ }
grep { ! exists $device_ips->{$_->port} } grep { $_->port }
@results; @results;
map { push @{ $port_map->{$_->port} }, $_ }
grep { $_->port }
$device->device_ips()->all;
foreach my $map (@{ setting('hide_deviceports')}) { foreach my $map (@{ setting('hide_deviceports')}) {
next unless ref {} eq ref $map; next unless ref {} eq ref $map;
foreach my $key (sort keys %$map) { foreach my $key (sort keys %$map) {
# lhs matches device, rhs matches port # lhs matches device, rhs matches port
next unless check_acl_no($device, $key); next unless $key and $map->{$key};
next unless acl_matches($device, $key);
PORT: foreach my $port (sort keys %$device_ips) { foreach my $port (sort keys %$port_map) {
foreach my $thing (@{ $device_ips->{$port} }) { next unless acl_matches($port_map->{$port}, $map->{$key});
next unless check_acl_no($thing, $map->{$key}); ++$to_hide{$port};
}
}
}
@results = grep { $_->port ne $port } @results; @results = grep { ! exists $to_hide{$_->port} } @results;
next PORT;
}
}
}
}
} }
# sort ports # sort ports

View File

@@ -3,7 +3,7 @@ package App::Netdisco::Worker::Plugin;
use Dancer ':syntax'; use Dancer ':syntax';
use Dancer::Plugin; use Dancer::Plugin;
use App::Netdisco::Util::Permission qw/check_acl_no check_acl_only/; use App::Netdisco::Util::Permission qw/acl_matches acl_matches_only/;
use aliased 'App::Netdisco::Worker::Status'; use aliased 'App::Netdisco::Worker::Status';
use Scope::Guard 'guard'; use Scope::Guard 'guard';
use Storable 'dclone'; use Storable 'dclone';
@@ -73,8 +73,8 @@ register 'register_worker' => sub {
my $only = (exists $workerconf->{only} ? $workerconf->{only} : undef); my $only = (exists $workerconf->{only} ? $workerconf->{only} : undef);
return $job->add_status( Status->info('skip: acls restricted') ) return $job->add_status( Status->info('skip: acls restricted') )
if ($no and check_acl_no($job->device, $no)) if ($no and acl_matches($job->device, $no))
or ($only and not check_acl_only($job->device, $only)); or ($only and not acl_matches_only($job->device, $only));
# reduce device_auth by driver and action filters # reduce device_auth by driver and action filters
foreach my $stanza (@userconf) { foreach my $stanza (@userconf) {

View File

@@ -5,7 +5,7 @@ use App::Netdisco::Worker::Plugin;
use aliased 'App::Netdisco::Worker::Status'; use aliased 'App::Netdisco::Worker::Status';
use App::Netdisco::Util::Worker; use App::Netdisco::Util::Worker;
use App::Netdisco::Util::Permission qw/check_acl_no check_acl_only/; use App::Netdisco::Util::Permission qw/acl_matches acl_matches_only/;
register_worker({ phase => 'late' }, sub { register_worker({ phase => 'late' }, sub {
my ($job, $workerconf) = @_; my ($job, $workerconf) = @_;
@@ -21,8 +21,8 @@ register_worker({ phase => 'late' }, sub {
my $no = ($conf->{'filter'}->{'no'} || []); my $no = ($conf->{'filter'}->{'no'} || []);
my $only = ($conf->{'filter'}->{'only'} || []); my $only = ($conf->{'filter'}->{'only'} || []);
next if check_acl_no( $job->device, $no ); next if acl_matches( $job->device, $no );
next unless check_acl_only( $job->device, $only); next unless acl_matches_only( $job->device, $only);
if ($conf->{'event'} eq 'arpnip') { if ($conf->{'event'} eq 'arpnip') {
$count += queue_hook('arpnip', $conf); $count += queue_hook('arpnip', $conf);

View File

@@ -5,7 +5,7 @@ use App::Netdisco::Worker::Plugin;
use aliased 'App::Netdisco::Worker::Status'; use aliased 'App::Netdisco::Worker::Status';
use App::Netdisco::Transport::SNMP (); use App::Netdisco::Transport::SNMP ();
use App::Netdisco::Util::Permission 'check_acl_no'; use App::Netdisco::Util::Permission 'acl_matches';
use Dancer::Plugin::DBIC 'schema'; use Dancer::Plugin::DBIC 'schema';
use NetAddr::IP::Lite ':lower'; use NetAddr::IP::Lite ':lower';
use Time::HiRes 'gettimeofday'; use Time::HiRes 'gettimeofday';
@@ -42,7 +42,7 @@ sub gather_subnets {
my $addr = $ip->addr; my $addr = $ip->addr;
next if $addr eq '0.0.0.0'; next if $addr eq '0.0.0.0';
next if check_acl_no($ip, 'group:__LOOPBACK_ADDRESSES__'); next if acl_matches($ip, 'group:__LOOPBACK_ADDRESSES__');
next if setting('ignore_private_nets') and $ip->is_rfc1918; next if setting('ignore_private_nets') and $ip->is_rfc1918;
my $netmask = $ip_netmask->{$addr} || $ip->bits(); my $netmask = $ip_netmask->{$addr} || $ip->bits();

View File

@@ -5,7 +5,7 @@ use App::Netdisco::Worker::Plugin;
use aliased 'App::Netdisco::Worker::Status'; use aliased 'App::Netdisco::Worker::Status';
use App::Netdisco::Util::Worker; use App::Netdisco::Util::Worker;
use App::Netdisco::Util::Permission qw/check_acl_no check_acl_only/; use App::Netdisco::Util::Permission qw/acl_matches acl_matches_only/;
register_worker({ phase => 'late' }, sub { register_worker({ phase => 'late' }, sub {
my ($job, $workerconf) = @_; my ($job, $workerconf) = @_;
@@ -21,8 +21,8 @@ register_worker({ phase => 'late' }, sub {
my $no = ($conf->{'filter'}->{'no'} || []); my $no = ($conf->{'filter'}->{'no'} || []);
my $only = ($conf->{'filter'}->{'only'} || []); my $only = ($conf->{'filter'}->{'only'} || []);
next if check_acl_no( $job->device, $no ); next if acl_matches( $job->device, $no );
next unless check_acl_only( $job->device, $only); next unless acl_matches_only( $job->device, $only);
if ($conf->{'event'} eq 'delete') { if ($conf->{'event'} eq 'delete') {
$count += queue_hook('delete', $conf); $count += queue_hook('delete', $conf);

View File

@@ -5,7 +5,7 @@ use App::Netdisco::Worker::Plugin;
use aliased 'App::Netdisco::Worker::Status'; use aliased 'App::Netdisco::Worker::Status';
use App::Netdisco::Transport::SNMP (); use App::Netdisco::Transport::SNMP ();
use App::Netdisco::Util::Permission 'check_acl_no'; use App::Netdisco::Util::Permission 'acl_matches';
use App::Netdisco::Util::DNS 'ipv4_from_hostname'; use App::Netdisco::Util::DNS 'ipv4_from_hostname';
use App::Netdisco::Util::Device 'is_discoverable'; use App::Netdisco::Util::Device 'is_discoverable';
use Dancer::Plugin::DBIC 'schema'; use Dancer::Plugin::DBIC 'schema';
@@ -34,18 +34,21 @@ register_worker({ phase => 'main', driver => 'snmp' }, sub {
if (scalar @{ setting('device_identity') }) { if (scalar @{ setting('device_identity') }) {
my @idmaps = @{ setting('device_identity') }; my @idmaps = @{ setting('device_identity') };
my $devips = $device->device_ips->order_by('alias'); my @devips = $device->device_ips->order_by('alias')->all;
ALIAS: while (my $alias = $devips->next) { # using ALIASMAP break so that we stop after first successful renumber
next if $alias->alias eq $old_ip;
foreach my $map (@idmaps) { ALIASMAP: foreach my $map (@idmaps) {
next unless ref {} eq ref $map; next unless ref {} eq ref $map;
foreach my $key (sort keys %$map) { foreach my $key (sort keys %$map) {
# lhs matches device, rhs matches device_ip # lhs matches device, rhs matches device_ip
if (check_acl_no($device, $key) next unless $key and $map->{$key};
and check_acl_no($alias, $map->{$key})) { next unless acl_matches($device, $key);
foreach my $alias (@devips) {
next if $alias->alias eq $old_ip;
next unless acl_matches($alias, $map->{$key});
if (not is_discoverable( $alias->alias )) { if (not is_discoverable( $alias->alias )) {
debug sprintf ' [%s] device - cannot renumber to %s - not discoverable', debug sprintf ' [%s] device - cannot renumber to %s - not discoverable',
@@ -55,7 +58,7 @@ register_worker({ phase => 'main', driver => 'snmp' }, sub {
if (App::Netdisco::Transport::SNMP->test_connection( $alias->alias )) { if (App::Netdisco::Transport::SNMP->test_connection( $alias->alias )) {
$new_ip = $alias->alias; $new_ip = $alias->alias;
last ALIAS; last ALIASMAP;
} }
else { else {
debug sprintf ' [%s] device - cannot renumber to %s - SNMP connect failed', debug sprintf ' [%s] device - cannot renumber to %s - SNMP connect failed',
@@ -64,7 +67,6 @@ register_worker({ phase => 'main', driver => 'snmp' }, sub {
} }
} }
} }
} # ALIAS
} }
return if $new_ip eq $old_ip; return if $new_ip eq $old_ip;

View File

@@ -5,7 +5,7 @@ use App::Netdisco::Worker::Plugin;
use aliased 'App::Netdisco::Worker::Status'; use aliased 'App::Netdisco::Worker::Status';
use App::Netdisco::Util::Worker; use App::Netdisco::Util::Worker;
use App::Netdisco::Util::Permission qw/check_acl_no check_acl_only/; use App::Netdisco::Util::Permission qw/acl_matches acl_matches_only/;
register_worker({ phase => 'late' }, sub { register_worker({ phase => 'late' }, sub {
my ($job, $workerconf) = @_; my ($job, $workerconf) = @_;
@@ -21,8 +21,8 @@ register_worker({ phase => 'late' }, sub {
my $no = ($conf->{'filter'}->{'no'} || []); my $no = ($conf->{'filter'}->{'no'} || []);
my $only = ($conf->{'filter'}->{'only'} || []); my $only = ($conf->{'filter'}->{'only'} || []);
next if check_acl_no( $job->device, $no ); next if acl_matches( $job->device, $no );
next unless check_acl_only( $job->device, $only); next unless acl_matches_only( $job->device, $only);
if (vars->{'new_device'} and $conf->{'event'} eq 'new_device') { if (vars->{'new_device'} and $conf->{'event'} eq 'new_device') {
$count += queue_hook('new_device', $conf); $count += queue_hook('new_device', $conf);

View File

@@ -6,7 +6,7 @@ use aliased 'App::Netdisco::Worker::Status';
use App::Netdisco::Transport::SNMP (); use App::Netdisco::Transport::SNMP ();
use App::Netdisco::Util::Device qw/get_device is_discoverable/; use App::Netdisco::Util::Device qw/get_device is_discoverable/;
use App::Netdisco::Util::Permission 'check_acl_no'; use App::Netdisco::Util::Permission 'acl_matches';
use App::Netdisco::JobQueue 'jq_insert'; use App::Netdisco::JobQueue 'jq_insert';
use Dancer::Plugin::DBIC 'schema'; use Dancer::Plugin::DBIC 'schema';
use List::MoreUtils (); use List::MoreUtils ();
@@ -185,7 +185,7 @@ sub store_neighbors {
# useable remote IP... # useable remote IP...
if ((! $r_netaddr) or ($remote_ip eq '0.0.0.0') or if ((! $r_netaddr) or ($remote_ip eq '0.0.0.0') or
check_acl_no($remote_ip, 'group:__LOOPBACK_ADDRESSES__')) { acl_matches($remote_ip, 'group:__LOOPBACK_ADDRESSES__')) {
if ($remote_id) { if ($remote_id) {
my $devices = schema('netdisco')->resultset('Device'); my $devices = schema('netdisco')->resultset('Device');

View File

@@ -6,7 +6,7 @@ use App::Netdisco::Transport::SNMP;
use aliased 'App::Netdisco::Worker::Status'; use aliased 'App::Netdisco::Worker::Status';
use App::Netdisco::Util::Device qw/get_device is_discoverable/; use App::Netdisco::Util::Device qw/get_device is_discoverable/;
use App::Netdisco::Util::Permission 'check_acl_no'; use App::Netdisco::Util::Permission 'acl_matches';
use App::Netdisco::JobQueue 'jq_insert'; use App::Netdisco::JobQueue 'jq_insert';
register_worker({ phase => 'main', driver => 'snmp' }, sub { register_worker({ phase => 'main', driver => 'snmp' }, sub {
@@ -15,8 +15,8 @@ register_worker({ phase => 'main', driver => 'snmp' }, sub {
my $device = $job->device; my $device = $job->device;
return unless $device->in_storage and ($device->has_layer(3) return unless $device->in_storage and ($device->has_layer(3)
or check_acl_no($device, 'force_macsuck') or acl_matches($device, 'force_arpnip')
or check_acl_no($device, 'ignore_layers')); or acl_matches($device, 'ignore_layers'));
my $snmp = App::Netdisco::Transport::SNMP->reader_for($device) my $snmp = App::Netdisco::Transport::SNMP->reader_for($device)
or return Status->defer("discover failed: could not SNMP connect to $device"); or return Status->defer("discover failed: could not SNMP connect to $device");

View File

@@ -8,6 +8,8 @@ use App::Netdisco::Transport::SNMP ();
use Dancer::Plugin::DBIC 'schema'; use Dancer::Plugin::DBIC 'schema';
use Encode; use Encode;
use App::Netdisco::Util::Web 'sort_port';
use App::Netdisco::Util::Permission 'acl_matches';
use App::Netdisco::Util::FastResolver 'hostnames_resolve_async'; use App::Netdisco::Util::FastResolver 'hostnames_resolve_async';
use App::Netdisco::Util::Device qw/is_discoverable match_to_setting/; use App::Netdisco::Util::Device qw/is_discoverable match_to_setting/;
@@ -131,6 +133,28 @@ register_worker({ phase => 'main', driver => 'snmp' }, sub {
$properties{ $port }->{remote_serial} = $rem_serial->{ $idx }; $properties{ $port }->{remote_serial} = $rem_serial->{ $idx };
} }
if (scalar @{ setting('ignore_deviceports') }) {
foreach my $map (@{ setting('ignore_deviceports')}) {
next unless ref {} eq ref $map;
foreach my $key (sort keys %$map) {
# lhs matches device, rhs matches port
next unless $key and $map->{$key};
next unless acl_matches($device, $key);
foreach my $port (sort { sort_port($a, $b) } keys %properties) {
next unless acl_matches([$properties{$port}, $device_ports->{$port}],
$map->{$key});
debug sprintf ' [%s] properties - removing %s (config:ignore_deviceports)',
$device->ip, $port;
$device_ports->{$port}->delete; # like, for real, in the DB
delete $properties{$port};
}
}
}
}
foreach my $idx (keys %$interfaces) { foreach my $idx (keys %$interfaces) {
next unless defined $idx; next unless defined $idx;
my $port = $interfaces->{$idx} or next; my $port = $interfaces->{$idx} or next;
@@ -155,7 +179,7 @@ register_worker({ phase => 'main', driver => 'snmp' }, sub {
schema('netdisco')->txn_do(sub { schema('netdisco')->txn_do(sub {
my $gone = $device->properties_ports->delete; my $gone = $device->properties_ports->delete;
debug sprintf ' [%s] properties - removed %d ports with properties', debug sprintf ' [%s] properties - removed %d port properties',
$device->ip, $gone; $device->ip, $gone;
$device->properties_ports->populate( $device->properties_ports->populate(
[map {{ port => $_, %{ $properties{$_} } }} keys %properties] ); [map {{ port => $_, %{ $properties{$_} } }} keys %properties] );

View File

@@ -5,14 +5,16 @@ use App::Netdisco::Worker::Plugin;
use aliased 'App::Netdisco::Worker::Status'; use aliased 'App::Netdisco::Worker::Status';
use App::Netdisco::Transport::SNMP (); use App::Netdisco::Transport::SNMP ();
use App::Netdisco::Util::Permission qw/check_acl_no check_acl_only/; use App::Netdisco::Util::Permission qw/acl_matches acl_matches_only/;
use App::Netdisco::Util::FastResolver 'hostnames_resolve_async'; use App::Netdisco::Util::FastResolver 'hostnames_resolve_async';
use App::Netdisco::Util::Device 'get_device'; use App::Netdisco::Util::Device 'get_device';
use App::Netdisco::Util::DNS 'hostname_from_ip'; use App::Netdisco::Util::DNS 'hostname_from_ip';
use App::Netdisco::Util::SNMP 'snmp_comm_reindex'; use App::Netdisco::Util::SNMP 'snmp_comm_reindex';
use App::Netdisco::Util::Web 'sort_port';
use Dancer::Plugin::DBIC 'schema'; use Dancer::Plugin::DBIC 'schema';
use Scope::Guard 'guard'; use Scope::Guard 'guard';
use NetAddr::IP::Lite ':lower'; use NetAddr::IP::Lite ':lower';
use Storable 'dclone';
use Encode; use Encode;
register_worker({ phase => 'early', driver => 'snmp' }, sub { register_worker({ phase => 'early', driver => 'snmp' }, sub {
@@ -66,7 +68,7 @@ register_worker({ phase => 'early', driver => 'snmp' }, sub {
my $protect = setting('snmp_field_protection')->{'device'} || {}; my $protect = setting('snmp_field_protection')->{'device'} || {};
my %dirty = $device->get_dirty_columns; my %dirty = $device->get_dirty_columns;
foreach my $field (keys %dirty) { foreach my $field (keys %dirty) {
next unless check_acl_only($ip, $protect->{$field}); next unless acl_matches_only($ip, $protect->{$field});
if (!defined $dirty{$field} or $dirty{$field} eq '') { if (!defined $dirty{$field} or $dirty{$field} eq '') {
return $job->cancel("discover cancelled: $ip failed to return valid $field"); return $job->cancel("discover cancelled: $ip failed to return valid $field");
} }
@@ -182,13 +184,6 @@ register_worker({ phase => 'early', driver => 'snmp' }, sub {
my $snmp = App::Netdisco::Transport::SNMP->reader_for($device) my $snmp = App::Netdisco::Transport::SNMP->reader_for($device)
or return Status->defer("discover failed: could not SNMP connect to $device"); or return Status->defer("discover failed: could not SNMP connect to $device");
# gather device_ips for use in ACLs later
my $device_ips = {};
foreach my $dip ($device->device_ips()->all) {
next unless defined $dip->port and $dip->port;
push @{ $device_ips->{ $dip->port } }, $dip;
}
my $interfaces = $snmp->interfaces; my $interfaces = $snmp->interfaces;
my $i_type = $snmp->i_type; my $i_type = $snmp->i_type;
my $i_ignore = $snmp->i_ignore; my $i_ignore = $snmp->i_ignore;
@@ -272,38 +267,29 @@ register_worker({ phase => 'early', driver => 'snmp' }, sub {
} }
if (scalar @{ setting('ignore_deviceports') }) { if (scalar @{ setting('ignore_deviceports') }) {
foreach my $port (keys %$device_ips) { my $port_map = {};
if (!exists $deviceports{$port}) {
delete $device_ips->{$port}; map { push @{ $port_map->{ $_->port } }, $_ }
next; grep { $_->port }
} $device->device_ips()->all;
foreach my $dip (@{ $device_ips->{$port} }) {
$dip->set_inflated_columns({ device_port => $deviceports{$port} }); map { push @{ $port_map->{ $_->{port} } }, $_ }
} values %{ dclone (\%deviceports || {}) };
}
foreach my $port (keys %deviceports) {
next if exists $device_ips->{$port};
push @{ $device_ips->{$port} },
schema('netdisco')->resultset('DevicePort')
->new_result( $deviceports{$port} );
}
foreach my $map (@{ setting('ignore_deviceports')}) { foreach my $map (@{ setting('ignore_deviceports')}) {
next unless ref {} eq ref $map; next unless ref {} eq ref $map;
foreach my $key (sort keys %$map) { foreach my $key (sort keys %$map) {
# lhs matches device, rhs matches port # lhs matches device, rhs matches port
next unless check_acl_no($device, $key); next unless $key and $map->{$key};
next unless acl_matches($device, $key);
PORT: foreach my $port (sort keys %$device_ips) { foreach my $port (sort { sort_port( $a, $b) } keys %$port_map) {
foreach my $thing (@{ $device_ips->{$port} }) { next unless acl_matches($port_map->{$port}, $map->{$key});
next unless check_acl_no($thing, $map->{$key});
debug sprintf ' [%s] interfaces - ignoring %s (config:ignore_deviceports)', debug sprintf ' [%s] interfaces - ignoring %s (config:ignore_deviceports)',
$device->ip, $port; $device->ip, $port;
delete $deviceports{$port}; delete $deviceports{$port};
next PORT;
}
} }
} }
} }
@@ -446,7 +432,7 @@ sub _get_ipv4_aliases {
my $addr = $ip->addr; my $addr = $ip->addr;
next if $addr eq '0.0.0.0'; next if $addr eq '0.0.0.0';
next if check_acl_no($ip, 'group:__LOOPBACK_ADDRESSES__'); next if acl_matches($ip, 'group:__LOOPBACK_ADDRESSES__');
next if setting('ignore_private_nets') and $ip->is_rfc1918; next if setting('ignore_private_nets') and $ip->is_rfc1918;
my $iid = $ip_index->{$addr}; my $iid = $ip_index->{$addr};
@@ -499,7 +485,7 @@ sub _get_ipv6_aliases {
my $addr = $ip->addr; my $addr = $ip->addr;
next if $addr eq '::0'; next if $addr eq '::0';
next if check_acl_no($ip, 'group:__LOOPBACK_ADDRESSES__'); next if acl_matches($ip, 'group:__LOOPBACK_ADDRESSES__');
my $port = $interfaces->{ $ipv6_index->{$iid} }; my $port = $interfaces->{ $ipv6_index->{$iid} };
my $subnet = $ipv6_pfxlen->{$iid} my $subnet = $ipv6_pfxlen->{$iid}

View File

@@ -5,7 +5,7 @@ use App::Netdisco::Worker::Plugin;
use aliased 'App::Netdisco::Worker::Status'; use aliased 'App::Netdisco::Worker::Status';
use App::Netdisco::JobQueue 'jq_insert'; use App::Netdisco::JobQueue 'jq_insert';
use App::Netdisco::Util::Permission 'check_acl_no'; use App::Netdisco::Util::Permission 'acl_matches';
register_worker({ phase => 'main' }, sub { register_worker({ phase => 'main' }, sub {
my ($job, $workerconf) = @_; my ($job, $workerconf) = @_;
@@ -16,8 +16,8 @@ register_worker({ phase => 'main' }, sub {
return unless $device->in_storage and $job->subaction eq 'with-nodes'; return unless $device->in_storage and $job->subaction eq 'with-nodes';
if (!defined $device->last_macsuck and ($device->has_layer(2) if (!defined $device->last_macsuck and ($device->has_layer(2)
or check_acl_no($device, 'force_macsuck') or acl_matches($device, 'force_macsuck')
or check_acl_no($device, 'ignore_layers'))) { or acl_matches($device, 'ignore_layers'))) {
jq_insert({ jq_insert({
device => $device->ip, device => $device->ip,
action => 'macsuck', action => 'macsuck',
@@ -28,8 +28,8 @@ register_worker({ phase => 'main' }, sub {
} }
if (!defined $device->last_arpnip and ($device->has_layer(3) if (!defined $device->last_arpnip and ($device->has_layer(3)
or check_acl_no($device, 'force_arpnip') or acl_matches($device, 'force_arpnip')
or check_acl_no($device, 'ignore_layers'))) { or acl_matches($device, 'ignore_layers'))) {
jq_insert({ jq_insert({
device => $device->ip, device => $device->ip,
action => 'arpnip', action => 'arpnip',

View File

@@ -5,7 +5,7 @@ use App::Netdisco::Worker::Plugin;
use aliased 'App::Netdisco::Worker::Status'; use aliased 'App::Netdisco::Worker::Status';
use App::Netdisco::Util::Worker; use App::Netdisco::Util::Worker;
use App::Netdisco::Util::Permission qw/check_acl_no check_acl_only/; use App::Netdisco::Util::Permission qw/acl_matches acl_matches_only/;
register_worker({ phase => 'late' }, sub { register_worker({ phase => 'late' }, sub {
my ($job, $workerconf) = @_; my ($job, $workerconf) = @_;
@@ -21,8 +21,8 @@ register_worker({ phase => 'late' }, sub {
my $no = ($conf->{'filter'}->{'no'} || []); my $no = ($conf->{'filter'}->{'no'} || []);
my $only = ($conf->{'filter'}->{'only'} || []); my $only = ($conf->{'filter'}->{'only'} || []);
next if check_acl_no( $job->device, $no ); next if acl_matches( $job->device, $no );
next unless check_acl_only( $job->device, $only); next unless acl_matches_only( $job->device, $only);
if ($conf->{'event'} eq 'macsuck') { if ($conf->{'event'} eq 'macsuck') {
$count += queue_hook('macsuck', $conf); $count += queue_hook('macsuck', $conf);

View File

@@ -5,11 +5,12 @@ use App::Netdisco::Worker::Plugin;
use aliased 'App::Netdisco::Worker::Status'; use aliased 'App::Netdisco::Worker::Status';
use App::Netdisco::Transport::SNMP (); use App::Netdisco::Transport::SNMP ();
use App::Netdisco::Util::Permission qw/check_acl_no check_acl_only/; use App::Netdisco::Util::Permission 'acl_matches';
use App::Netdisco::Util::PortMAC 'get_port_macs'; use App::Netdisco::Util::PortMAC 'get_port_macs';
use App::Netdisco::Util::Device 'match_to_setting'; use App::Netdisco::Util::Device 'match_to_setting';
use App::Netdisco::Util::Node 'check_mac'; use App::Netdisco::Util::Node 'check_mac';
use App::Netdisco::Util::SNMP 'snmp_comm_reindex'; use App::Netdisco::Util::SNMP 'snmp_comm_reindex';
use App::Netdisco::Util::Web 'sort_port';
use Dancer::Plugin::DBIC 'schema'; use Dancer::Plugin::DBIC 'schema';
use Time::HiRes 'gettimeofday'; use Time::HiRes 'gettimeofday';
@@ -37,7 +38,8 @@ register_worker({ phase => 'early',
# cache the device ports to save hitting the database for many single rows # cache the device ports to save hitting the database for many single rows
vars->{'device_ports'} = {map {($_->port => $_)} vars->{'device_ports'} = {map {($_->port => $_)}
$device->ports(undef, {prefetch => {neighbor_alias => 'device'}})->all}; $device->ports(undef, {prefetch => ['properties',
{neighbor_alias => 'device'}]})->all};
}); });
register_worker({ phase => 'main', driver => 'direct', register_worker({ phase => 'main', driver => 'direct',
@@ -488,14 +490,15 @@ sub sanity_macs {
foreach my $key (sort keys %$map) { foreach my $key (sort keys %$map) {
# lhs matches device, rhs matches port # lhs matches device, rhs matches port
next unless check_acl_only($device, $key); next unless $key and $map->{$key};
next unless acl_matches($device, $key);
foreach my $port (keys %{ $device_ports }) { foreach my $port (sort { sort_port($a, $b) } keys %{ $device_ports }) {
next unless check_acl_only($device_ports->{$port}, $map->{$key}); next unless acl_matches($device_ports->{$port}, $map->{$key});
++$ignoreport->{$port};
debug sprintf ' [%s] macsuck %s - port suppressed by macsuck_no_deviceports', debug sprintf ' [%s] macsuck %s - port suppressed by macsuck_no_deviceports',
$device->ip, $port; $device->ip, $port;
++$ignoreport->{$port};
} }
} }
} }
@@ -559,7 +562,7 @@ sub sanity_macs {
# with device lookups. # with device lookups.
my $neigh_cannot_macsuck = eval { # can fail my $neigh_cannot_macsuck = eval { # can fail
check_acl_no(($device_port->neighbor || "0 but true"), 'macsuck_unsupported') || acl_matches(($device_port->neighbor || "0 but true"), 'macsuck_unsupported') ||
match_to_setting($device_port->remote_type, 'macsuck_unsupported_type') }; match_to_setting($device_port->remote_type, 'macsuck_unsupported_type') };
# here, is_uplink comes from Discover::Neighbors finding LLDP remnants # here, is_uplink comes from Discover::Neighbors finding LLDP remnants

View File

@@ -12,7 +12,7 @@ use aliased 'App::Netdisco::Worker::Status';
use Path::Class; use Path::Class;
use List::Util qw/pairkeys pairfirst/; use List::Util qw/pairkeys pairfirst/;
use File::Slurper qw/read_lines write_text/; use File::Slurper qw/read_lines write_text/;
use App::Netdisco::Util::Permission 'check_acl_no'; use App::Netdisco::Util::Permission 'acl_matches';
register_worker({ phase => 'main' }, sub { register_worker({ phase => 'main' }, sub {
my ($job, $workerconf) = @_; my ($job, $workerconf) = @_;
@@ -65,16 +65,16 @@ register_worker({ phase => 'main' }, sub {
my $routerunicity = {}; my $routerunicity = {};
while (my $d = $devices->next) { while (my $d = $devices->next) {
if (check_acl_no($d, $config->{excluded})) { if (acl_matches($d, $config->{excluded})) {
debug " skipping $d: device excluded of export"; debug " skipping $d: device excluded of export";
next next
} }
my $name = check_acl_no($d, $config->{by_ip}) ? $d->ip : ($d->dns || $d->name); my $name = acl_matches($d, $config->{by_ip}) ? $d->ip : ($d->dns || $d->name);
$name =~ s/$domain_suffix$// if check_acl_no($d, $config->{by_hostname}); $name =~ s/$domain_suffix$// if acl_matches($d, $config->{by_hostname});
my ($group) = my ($group) =
(pairkeys pairfirst { check_acl_no($d, $b) } %{ $config->{groups} }) || $default_group; (pairkeys pairfirst { acl_matches($d, $b) } %{ $config->{groups} }) || $default_group;
if (exists($routerunicity->{$group}->{$name})) { if (exists($routerunicity->{$group}->{$name})) {
debug " skipping $d: device excluded because already present in export list"; debug " skipping $d: device excluded because already present in export list";
@@ -82,7 +82,7 @@ register_worker({ phase => 'main' }, sub {
} }
my ($vendor) = my ($vendor) =
(pairkeys pairfirst { check_acl_no($d, $b) } %{ $config->{vendormap} }) (pairkeys pairfirst { acl_matches($d, $b) } %{ $config->{vendormap} })
|| $d->vendor; || $d->vendor;
if (not ($name and $vendor)) { if (not ($name and $vendor)) {

View File

@@ -3,7 +3,7 @@ package App::Netdisco::Worker::Runner;
use Dancer qw/:moose :syntax/; use Dancer qw/:moose :syntax/;
use Dancer::Plugin::DBIC 'schema'; use Dancer::Plugin::DBIC 'schema';
use App::Netdisco::Util::Device 'get_device'; use App::Netdisco::Util::Device 'get_device';
use App::Netdisco::Util::Permission qw/check_acl_no check_acl_only/; use App::Netdisco::Util::Permission qw/acl_matches acl_matches_only/;
use aliased 'App::Netdisco::Worker::Status'; use aliased 'App::Netdisco::Worker::Status';
use Try::Tiny; use Try::Tiny;
@@ -43,8 +43,8 @@ sub run {
my $no = (exists $stanza->{no} ? $stanza->{no} : undef); my $no = (exists $stanza->{no} ? $stanza->{no} : undef);
my $only = (exists $stanza->{only} ? $stanza->{only} : undef); my $only = (exists $stanza->{only} ? $stanza->{only} : undef);
next if $no and check_acl_no($job->device, $no); next if $no and acl_matches($job->device, $no);
next if $only and not check_acl_only($job->device, $only); next if $only and not acl_matches_only($job->device, $only);
push @newuserconf, dclone $stanza; push @newuserconf, dclone $stanza;
} }

View File

@@ -8,8 +8,8 @@ use Test::More 1.302083;
use Test::File::ShareDir::Dist { 'App-Netdisco' => 'share/' }; use Test::File::ShareDir::Dist { 'App-Netdisco' => 'share/' };
BEGIN { BEGIN {
use_ok( 'App::Netdisco::Configuration', 'check_acl' ); use_ok( 'App::Netdisco::Configuration', 'acl_matches' );
use_ok( 'App::Netdisco::Util::Permission', 'check_acl' ); use_ok( 'App::Netdisco::Util::Permission', 'acl_matches' );
} }
use Dancer qw/:script !pass/; use Dancer qw/:script !pass/;
@@ -37,79 +37,79 @@ my @conf = (
); );
# name, ipv4, ipv6, v4 prefix, v6 prefix # name, ipv4, ipv6, v4 prefix, v6 prefix
ok(check_acl('localhost',[$conf[0]]), 'same name'); ok(acl_matches('localhost',[$conf[0]]), 'same name');
ok(check_acl('127.0.0.1',[$conf[2]]), 'same ipv4'); ok(acl_matches('127.0.0.1',[$conf[2]]), 'same ipv4');
ok(check_acl('::1',[$conf[4]]), 'same ipv6'); ok(acl_matches('::1',[$conf[4]]), 'same ipv6');
ok(check_acl('127.0.0.0/29',[$conf[6]]), 'same v4 prefix'); ok(acl_matches('127.0.0.0/29',[$conf[6]]), 'same v4 prefix');
ok(check_acl('::1/128',[$conf[8]]), 'same v6 prefix'); ok(acl_matches('::1/128',[$conf[8]]), 'same v6 prefix');
# failed name, ipv4, ipv6, v4 prefix, v6 prefix # failed name, ipv4, ipv6, v4 prefix, v6 prefix
is(check_acl('www.microsoft.com',[$conf[0]]), 0, 'failed name'); is(acl_matches('www.microsoft.com',[$conf[0]]), 0, 'failed name');
is(check_acl('172.20.0.1',[$conf[2]]), 0, 'failed ipv4'); is(acl_matches('172.20.0.1',[$conf[2]]), 0, 'failed ipv4');
is(check_acl('2001:db8::5',[$conf[4]]), 0, 'failed ipv6'); is(acl_matches('2001:db8::5',[$conf[4]]), 0, 'failed ipv6');
is(check_acl('172.16.1.3/29',[$conf[6]]), 0, 'failed v4 prefix'); is(acl_matches('172.16.1.3/29',[$conf[6]]), 0, 'failed v4 prefix');
is(check_acl('2001:db8:f00d::/64',[$conf[8]]), 0, 'failed v6 prefix'); is(acl_matches('2001:db8:f00d::/64',[$conf[8]]), 0, 'failed v6 prefix');
# negated name, ipv4, ipv6, v4 prefix, v6 prefix # negated name, ipv4, ipv6, v4 prefix, v6 prefix
ok(check_acl('localhost',[$conf[1]]), 'not same name'); ok(acl_matches('localhost',[$conf[1]]), 'not same name');
ok(check_acl('127.0.0.1',[$conf[3]]), 'not same ipv4'); ok(acl_matches('127.0.0.1',[$conf[3]]), 'not same ipv4');
ok(check_acl('::1',[$conf[5]]), 'not same ipv6'); ok(acl_matches('::1',[$conf[5]]), 'not same ipv6');
ok(check_acl('127.0.0.0/29',[$conf[7]]), 'not same v4 prefix'); ok(acl_matches('127.0.0.0/29',[$conf[7]]), 'not same v4 prefix');
ok(check_acl('::1/128',[$conf[9]]), 'not same v6 prefix'); ok(acl_matches('::1/128',[$conf[9]]), 'not same v6 prefix');
# v4 range, v6 range # v4 range, v6 range
ok(check_acl('127.0.0.1',[$conf[10]]), 'in v4 range'); ok(acl_matches('127.0.0.1',[$conf[10]]), 'in v4 range');
ok(check_acl('::1',[$conf[12]]), 'in v6 range'); ok(acl_matches('::1',[$conf[12]]), 'in v6 range');
# failed v4 range, v6 range # failed v4 range, v6 range
is(check_acl('172.20.0.1',[$conf[10]]), 0, 'failed v4 range'); is(acl_matches('172.20.0.1',[$conf[10]]), 0, 'failed v4 range');
is(check_acl('2001:db8::5',[$conf[12]]), 0, 'failed v6 range'); is(acl_matches('2001:db8::5',[$conf[12]]), 0, 'failed v6 range');
# negated v4 range, v6 range # negated v4 range, v6 range
ok(check_acl('127.0.0.1',[$conf[11]]), 'not in v4 range'); ok(acl_matches('127.0.0.1',[$conf[11]]), 'not in v4 range');
ok(check_acl('::1',[$conf[13]]), 'not in v6 range'); ok(acl_matches('::1',[$conf[13]]), 'not in v6 range');
# hostname regexp # hostname regexp
# FIXME ok(check_acl('localhost',[$conf[14]]), 'name regexp'); # FIXME ok(acl_matches('localhost',[$conf[14]]), 'name regexp');
# FIXME ok(check_acl('127.0.0.1',[$conf[14]]), 'IP regexp'); # FIXME ok(acl_matches('127.0.0.1',[$conf[14]]), 'IP regexp');
is(check_acl('www.google.com',[$conf[14]]), 0, 'failed regexp'); is(acl_matches('www.google.com',[$conf[14]]), 0, 'failed regexp');
# OR of prefix, range, regexp, property (2 of, 3 of, 4 of) # OR of prefix, range, regexp, property (2 of, 3 of, 4 of)
ok(check_acl('127.0.0.1',[@conf[8,0]]), 'OR: prefix, name'); ok(acl_matches('127.0.0.1',[@conf[8,0]]), 'OR: prefix, name');
ok(check_acl('127.0.0.1',[@conf[8,12,0]]), 'OR: prefix, range, name'); ok(acl_matches('127.0.0.1',[@conf[8,12,0]]), 'OR: prefix, range, name');
ok(check_acl('127.0.0.1',[@conf[8,12,15,0]]), 'OR: prefix, range, regexp, name'); ok(acl_matches('127.0.0.1',[@conf[8,12,15,0]]), 'OR: prefix, range, regexp, name');
# OR of negated prefix, range, regexp, property (2 of, 3 of, 4 of) # OR of negated prefix, range, regexp, property (2 of, 3 of, 4 of)
ok(check_acl('127.0.0.1',[@conf[17,0]]), 'OR: !prefix, name'); ok(acl_matches('127.0.0.1',[@conf[17,0]]), 'OR: !prefix, name');
ok(check_acl('127.0.0.1',[@conf[17,18,0]]), 'OR: !prefix, !range, name'); ok(acl_matches('127.0.0.1',[@conf[17,18,0]]), 'OR: !prefix, !range, name');
ok(check_acl('127.0.0.1',[@conf[17,18,19,0]]), 'OR: !prefix, !range, !regexp, name'); ok(acl_matches('127.0.0.1',[@conf[17,18,19,0]]), 'OR: !prefix, !range, !regexp, name');
# AND of prefix, range, regexp, property (2 of, 3 of, 4 of) # AND of prefix, range, regexp, property (2 of, 3 of, 4 of)
ok(check_acl('127.0.0.1',[@conf[6,0,20]]), 'AND: prefix, name'); ok(acl_matches('127.0.0.1',[@conf[6,0,20]]), 'AND: prefix, name');
ok(check_acl('127.0.0.1',[@conf[6,10,0,20]]), 'AND: prefix, range, name'); ok(acl_matches('127.0.0.1',[@conf[6,10,0,20]]), 'AND: prefix, range, name');
# FIXME ok(check_acl('127.0.0.1',[@conf[6,10,14,0,20]]), 'AND: prefix, range, regexp, name'); # FIXME ok(acl_matches('127.0.0.1',[@conf[6,10,14,0,20]]), 'AND: prefix, range, regexp, name');
# failed AND on prefix, range, regexp # failed AND on prefix, range, regexp
is(check_acl('127.0.0.1',[@conf[8,10,14,0,20]]), 0, 'failed AND: prefix!, range, regexp, name'); is(acl_matches('127.0.0.1',[@conf[8,10,14,0,20]]), 0, 'failed AND: prefix!, range, regexp, name');
is(check_acl('127.0.0.1',[@conf[6,12,14,0,20]]), 0, 'failed AND: prefix, range!, regexp, name'); is(acl_matches('127.0.0.1',[@conf[6,12,14,0,20]]), 0, 'failed AND: prefix, range!, regexp, name');
is(check_acl('127.0.0.1',[@conf[6,10,15,0,20]]), 0, 'failed AND: prefix, range, regexp!, name'); is(acl_matches('127.0.0.1',[@conf[6,10,15,0,20]]), 0, 'failed AND: prefix, range, regexp!, name');
# AND of negated prefix, range, regexp, property (2 of, 3 of, 4 of) # AND of negated prefix, range, regexp, property (2 of, 3 of, 4 of)
ok(check_acl('127.0.0.1',[@conf[9,0,20]]), 'AND: !prefix, name'); ok(acl_matches('127.0.0.1',[@conf[9,0,20]]), 'AND: !prefix, name');
ok(check_acl('127.0.0.1',[@conf[7,11,0,20]]), 'AND: !prefix, !range, name'); ok(acl_matches('127.0.0.1',[@conf[7,11,0,20]]), 'AND: !prefix, !range, name');
ok(check_acl('127.0.0.1',[@conf[9,13,16,0,20]]), 'AND: !prefix, !range, !regexp, name'); ok(acl_matches('127.0.0.1',[@conf[9,13,16,0,20]]), 'AND: !prefix, !range, !regexp, name');
# group ref # group ref
is(check_acl('192.0.2.1',[$conf[22]]), 1, '!missing group ref'); is(acl_matches('192.0.2.1',[$conf[22]]), 1, '!missing group ref');
is(check_acl('192.0.2.1',[$conf[21]]), 0, 'failed missing group ref'); is(acl_matches('192.0.2.1',[$conf[21]]), 0, 'failed missing group ref');
setting('host_groups')->{'groupreftest'} = ['192.0.2.1']; setting('host_groups')->{'groupreftest'} = ['192.0.2.1'];
is(check_acl('192.0.2.1',[$conf[21]]), 1, 'group ref'); is(acl_matches('192.0.2.1',[$conf[21]]), 1, 'group ref');
is(check_acl('192.0.2.1',[$conf[22]]), 0, 'failed !missing group ref'); is(acl_matches('192.0.2.1',[$conf[22]]), 0, 'failed !missing group ref');
# scalar promoted to list # scalar promoted to list
ok(check_acl('localhost',$conf[0]), 'scalar promoted'); ok(acl_matches('localhost',$conf[0]), 'scalar promoted');
ok(check_acl('localhost',$conf[1]), 'not scalar promoted'); ok(acl_matches('localhost',$conf[1]), 'not scalar promoted');
is(check_acl('www.microsoft.com',$conf[0]), 0, 'failed scalar promoted'); is(acl_matches('www.microsoft.com',$conf[0]), 0, 'failed scalar promoted');
use App::Netdisco::DB; use App::Netdisco::DB;
my $dip = App::Netdisco::DB->resultset('DeviceIp')->new_result({ my $dip = App::Netdisco::DB->resultset('DeviceIp')->new_result({
@@ -125,15 +125,80 @@ my $dip = App::Netdisco::DB->resultset('DeviceIp')->new_result({
}); });
# device properties # device properties
ok(check_acl($dip, [$conf[23]]), 'instance anon property deviceport:alias'); ok(acl_matches($dip, [$conf[23]]), '1obj instance anon property deviceport:alias');
ok(check_acl($dip, ['ip:'.$conf[2]]), 'instance named property deviceport:ip'); ok(acl_matches($dip, ['ip:'.$conf[2]]), '1obj instance named property deviceport:ip');
ok(check_acl($dip, ['!ip:'. $conf[23]]), 'negated instance named property deviceport:ip'); ok(acl_matches($dip, ['!ip:'. $conf[23]]), '1obj negated instance named property deviceport:ip');
is(check_acl($dip, ['port:'.$conf[2]]), 0, 'failed instance named property deviceport:ip'); is(acl_matches($dip, ['port:'.$conf[2]]), 0, '1obj failed instance named property deviceport:ip');
ok(check_acl($dip, ['port:.*GigabitEthernet.*']), 'instance named property regexp deviceport:port'); ok(acl_matches($dip, ['port:.*GigabitEthernet.*']), '1obj instance named property regexp deviceport:port');
ok(check_acl($dip, ['type:l3ipvlan']), 'related item field match'); # DeviceIp no longer has DevicePort slot accessors
ok(check_acl($dip, ['remote_ip:']), 'related item field empty'); #ok(acl_matches($dip, ['type:l3ipvlan']), '1obj related item field match');
ok(check_acl($dip, ['!type:']), 'related item field not empty'); #ok(acl_matches($dip, ['remote_ip:']), '1obj related item field empty');
is(check_acl($dip, ['foobar:xyz']), 0, 'unknown property'); #ok(acl_matches($dip, ['!type:']), '1obj related item field not empty');
#is(acl_matches($dip, ['foobar:xyz']), 0, '1obj unknown property');
my $dip2 = App::Netdisco::DB->resultset('DeviceIp')->new_result({
ip => '127.0.0.1',
port => 'TenGigabitEthernet1/10',
alias => '192.0.2.1',
});
my $dp = App::Netdisco::DB->resultset('DevicePort')->new_result({
ip => '127.0.0.1',
port => 'TenGigabitEthernet1/10',
type => 'l3ipvlan',
});
# device properties
ok(acl_matches([$dip2, $dp], [$conf[23]]), '2obj instance anon property deviceport:alias');
ok(acl_matches([$dip2, $dp], ['ip:'.$conf[2]]), '2obj instance named property deviceport:ip');
ok(acl_matches([undef, $dip2, $dp], ['ip:'.$conf[2]]), '2obj instance named property after undef');
ok(acl_matches([$dip2, $dp], ['!ip:'. $conf[23]]), '2obj negated instance named property deviceport:ip');
is(acl_matches([$dip2, $dp], ['port:'.$conf[2]]), 0, '2obj failed instance named property deviceport:ip');
ok(acl_matches([$dip2, $dp], ['port:.*GigabitEthernet.*']), '2obj instance named property regexp deviceport:port');
ok(acl_matches([$dip2, $dp], ['type:l3ipvlan']), '2obj related item field match');
ok(acl_matches([$dip2, $dp], ['remote_ip:']), '2obj related item field empty');
ok(acl_matches([$dip2, $dp], ['!type:']), '2obj related item field not empty');
is(acl_matches([$dip2, $dp], ['foobar:xyz']), 0, '2obj unknown property');
my $dip2c = { $dip2->get_inflated_columns };
my $dpc = { $dp->get_inflated_columns };
# device properties
ok(acl_matches([$dip2c, $dpc], [$conf[23]]), 'hh instance anon property deviceport:alias');
ok(acl_matches([$dip2c, $dpc], ['ip:'.$conf[2]]), 'hh instance named property deviceport:ip');
ok(acl_matches([$dip2c, $dpc], ['!ip:'. $conf[23]]), 'hh negated instance named property deviceport:ip');
is(acl_matches([$dip2c, $dpc], ['port:'.$conf[2]]), 0, 'hh failed instance named property deviceport:ip');
ok(acl_matches([$dip2c, $dpc], ['port:.*GigabitEthernet.*']), 'hh instance named property regexp deviceport:port');
ok(acl_matches([$dip2c, $dpc], ['type:l3ipvlan']), 'hh related item field match');
ok(acl_matches([$dip2c, $dpc], ['remote_ip:']), 'hh related item field empty');
ok(acl_matches([$dip2c, $dpc], ['!type:']), 'hh related item field not empty');
is(acl_matches([$dip2c, $dpc], ['foobar:xyz']), 0, 'hh unknown property');
# device properties
ok(acl_matches([$dip2, $dpc], [$conf[23]]), 'oh instance anon property deviceport:alias');
ok(acl_matches([$dip2, $dpc], ['ip:'.$conf[2]]), 'oh instance named property deviceport:ip');
ok(acl_matches([$dip2, $dpc], ['!ip:'. $conf[23]]), 'oh negated instance named property deviceport:ip');
is(acl_matches([$dip2, $dpc], ['port:'.$conf[2]]), 0, 'oh failed instance named property deviceport:ip');
ok(acl_matches([$dip2, $dpc], ['port:.*GigabitEthernet.*']), 'oh instance named property regexp deviceport:port');
ok(acl_matches([$dip2, $dpc], ['type:l3ipvlan']), 'oh related item field match');
ok(acl_matches([$dip2, $dpc], ['remote_ip:']), 'oh related item field empty');
ok(acl_matches([$dip2, $dpc], ['!type:']), 'oh related item field not empty');
is(acl_matches([$dip2, $dpc], ['foobar:xyz']), 0, 'oh unknown property');
# device properties
ok(acl_matches([$dip2c, $dp], [$conf[23]]), 'ho instance anon property deviceport:alias');
ok(acl_matches([$dip2c, $dp], ['ip:'.$conf[2]]), 'ho instance named property deviceport:ip');
ok(acl_matches([$dip2c, $dp], ['!ip:'. $conf[23]]), 'ho negated instance named property deviceport:ip');
is(acl_matches([$dip2c, $dp], ['port:'.$conf[2]]), 0, 'ho failed instance named property deviceport:ip');
ok(acl_matches([$dip2c, $dp], ['port:.*GigabitEthernet.*']), 'ho instance named property regexp deviceport:port');
ok(acl_matches([$dip2c, $dp], ['type:l3ipvlan']), 'ho related item field match');
ok(acl_matches([$dip2c, $dp], ['remote_ip:']), 'ho related item field empty');
ok(acl_matches([$dip2c, $dp], ['!type:']), 'ho related item field not empty');
is(acl_matches([$dip2c, $dp], ['foobar:xyz']), 0, 'ho unknown property');
done_testing; done_testing;