App-PaloAlto-PolicyVerify

 view release on metacpan or  search on metacpan

lib/App/PaloAlto/PolicyVerify.pm  view on Meta::CPAN

    open( $self->{fh}, '<:encoding(utf8)', $filepath )
      or die "Could not open file '$filepath'";

    return $self;
}

sub fields {
    my $self           = shift;
    my %column_numbers = @_;

    die "Five fields are required" unless ( keys %column_numbers == 5 );

    $self->{fields} =
      [ @column_numbers{qw(src_ip dst_ip src_port dst_port protocol)} ];

    return $self;
}

use constant {
    SRC_IP   => 0,
    DST_IP   => 1,
    SRC_PORT => 2,
    DST_PORT => 3,
    PROTO    => 4
};

{

    # Cache for src/dst IP, dst port, and proto flows.
    my %run_cache;

    sub run {
        my $self = shift;
        my $flow_cache_key;

        while ( my $row = $self->{csv}->getline( $self->{fh} ) ) {

            # Extract the flow fields
            my @flow_info = @{$row}[ @{ $self->{fields} } ];

            my $num_fields = grep { defined $_ } @flow_info;
            if ( $num_fields != 5 ) {
                die
"Only $num_fields fields extracted based on the separating character. We expect 5";
            }

            # Set up the cache key and return an entry if it exists
            # Note that we don't cache based on source port.
            $flow_cache_key =
              join( ':', @flow_info[ SRC_IP, DST_IP, DST_PORT, PROTO ] );

            # Pull the result out of cache, or we test
            # the security policy
            my $result =
              $run_cache{flow_cache_key} || $self->_test_sec_policy(@flow_info);
            next unless $result;

            # Add the result to the cache if needed.
            $run_cache{$flow_cache_key} //= $result;

            say $result->rulename . ','
              . $result->action . ','
              . $result->index . ',';
        }

    }

}

sub _test_sec_policy {
    my $self      = shift;
    my @flow_info = @_;

    # Find the zones for the source and dst IPs
    my $src_zone = $self->ip_to_zone( $flow_info[SRC_IP] ) or return;
    my $dst_zone = $self->ip_to_zone( $flow_info[DST_IP] ) or return;

    # Find the security policy
    my $result = $self->{fw}->test->sec_policy(
        from     => $src_zone,
        to       => $dst_zone,
        src_ip   => $flow_info[SRC_IP],
        dst_ip   => $flow_info[DST_IP],
        protocol => $flow_info[PROTO]
    );

    return $result;
}

{
    # Cache for FIB entries so we don't continuously make calls out to
    # fib_lookup
    my %fib_cache;

    # Cache the interfaces on the firewall
    my $interfaces;

    sub ip_to_zone {
        my $self = shift;
        my ($ip) = @_;

        # Find the egress interface from the FIB
        # We check if the entry exists because we want to know about
        # undefined routes that don't exist.
        my $fib_entry;
        if ( exists $fib_cache{$ip} ) {
            $fib_entry = $fib_cache{$ip};
        }
        else {
            $fib_entry = $fib_cache{$ip} = $self->{fw}->test->fib_lookup(
                ip             => $ip,
                virtual_router => $self->{vr}
            );
        }
        warn "No valid route for IP '$ip', skipping..." and return
          unless $fib_entry;

# FIXME: we're diving straight into the Device::Firewall::PaloAlto::Test::FIB's
# internal structure. Once its interface is better defined we'll go through that.
        my $fib_interface = $fib_entry->{entries}[0]{interface};



( run in 3.201 seconds using v1.01-cache-2.11-cpan-364913b4093 )