File Coverage

blib/lib/Device/Network/ConfigParser/Cisco/ASA.pm
Criterion Covered Total %
statement 35 69 50.7
branch 2 10 20.0
condition 4 4 100.0
subroutine 13 18 72.2
pod 5 5 100.0
total 59 106 55.6


line stmt bran cond sub pod time code
1             package Device::Network::ConfigParser::Cisco::ASA;
2             # ABSTRACT: Parse Cisco ASA Configuration
3             our $VERSION = '0.006'; # VERSION
4              
5 1     1   1518 use 5.006;
  1         3  
6 1     1   4 use strict;
  1         2  
  1         17  
7 1     1   4 use warnings;
  1         1  
  1         23  
8 1     1   240 use Modern::Perl;
  1         6244  
  1         6  
9 1     1   959 use Parse::RecDescent;
  1         24488  
  1         7  
10 1     1   483 use Data::Dumper;
  1         5122  
  1         93  
11 1     1   460 use JSON;
  1         6252  
  1         6  
12              
13 1     1   128 use Exporter qw{import};
  1         2  
  1         661  
14              
15             our @EXPORT_OK = qw{get_parser get_output_drivers parse_config post_process};
16              
17             =head1 NAME
18              
19             Device::Network::ConfigParser::Cisco::ASA - parse Cisco ASA configuration.
20              
21             =head1 VERSION
22              
23             version 0.006
24              
25             =head1 SYNOPSIS
26              
27             This module is intended to be used in conjunction with L, however there's nothing stopping it being used on its own.
28              
29             The module provides subroutines to parse & post-process Cisco ASA configuration, and output the structured data in a number of formats.
30              
31             =head1 SUBROUTINES
32              
33             =head2 get_parser
34              
35             For more information on the subroutine, see L.
36              
37             This module currently recognised the following parts of Cisco ASA configuration:
38              
39             =over 4
40              
41             =item * Hostname and domain name
42              
43             =item * Name aliases
44              
45             =item * Routes
46              
47             =item * Access lists
48              
49             =item * NATs
50              
51             =item * Objects
52              
53             =item * Object groups
54              
55             Any other lines within the file are classified as 'unrecognised'.
56              
57             =back
58              
59             =cut
60              
61             # This function is used with the (?) RecDescent operator, which returns an ARRAYREF.
62             # If there's an array member, it's returned.
63             # If not, the empty string is returned.
64             sub Parse::RecDescent::_zero_or_one {
65 31     31   60070 my ($array_ref, $action, $return) = @_;
66              
67 31   100     140 $return //= '';
68 31   100 13   175 $action //= sub { return $_[0]->[0] }; # By default, return the first member
  13         249  
69              
70 31 100       51 scalar @{ $array_ref } ? $action->($array_ref) : $return;
  31         384  
71             }
72            
73              
74              
75             sub get_parser {
76 1     1 1 462 return new Parse::RecDescent(q{
77            
78             startrule: config(s) { $item[1] }
79             config:
80             hostname { $item[1] } |
81             domain_name { $item[1] } |
82             name { $item[1] } |
83             route { $item[1] } |
84             acl { $item[1] } |
85             nat { $item[1] } |
86             object(s) { { type => 'object', config => $item{'object(s)'} } } |
87             object_group(s) { { type => 'object-group', config => $item[1] } } |
88             unrecognised { $item[1] }
89              
90             hostname: 'hostname' m{\w+} { { type => $item{__RULE__}, config => $item{__PATTERN1__} } }
91             domain_name: 'domain-name' m{\S+} {
92             { type => $item{__RULE__}, config => $item{__PATTERN1__} }
93             }
94              
95             name: 'name' ipv4 name_alias {
96             { type => $item{__RULE__}, config => { ip => $item{ipv4}, alias => $item{name_alias} } }
97             }
98              
99             route: 'route' interface network netmask next_hop metric track(?) {
100             { type => $item{__RULE__}, config => {
101             interface => $item{interface},
102             destination => $item{network}."/".$item{netmask},
103             next_hop => $item{next_hop},
104             metric => $item{metric},
105             track_id => @{ $item{'track(?)'} } ? $item{'track(?)'}->[0] : '',
106             }
107             }
108             }
109             network: alias_or_ipv4 { $item[1] }
110             next_hop: alias_or_ipv4 { $item[1] }
111             metric: m{\d{1,3}} { $item{__PATTERN1__} }
112             track: 'track' m{\d{1,3}} { $item{__PATTERN1__} }
113              
114             acl: 'access-list' m{\N+} { { type => 'acl', slurp => $item{__PATTERN1__} } }
115              
116             nat: 'nat' int_interface ext_interface source_nat destination_nat(?) proxy_arp(?) route_lookup(?) nat_description(?) {
117             { type => $item{__RULE__}, config => {
118             int_interface => $item{int_interface},
119             ext_interface => $item{ext_interface},
120             source_nat => $item{source_nat},
121             destination_nat => @{ $item{'destination_nat(?)'} } ? $item{'destination_nat(?)'}->[0] : {},
122             description => @{ $item{'nat_description(?)'} } ? $item{'nat_description(?)'}->[0] : '',
123             proxy_arp => @{ $item{'proxy_arp(?)'} } ? 0 : 1,
124             route_lookup => scalar(@{ $item{'route_lookup(?)'} }),
125             }
126             }
127             }
128             source_nat: 'source' m{static|dynamic} real_src mapped_src {
129             { type => $item{__PATTERN1__}, real_src => $item{real_src}, mapped_src => $item{mapped_src}, }
130             }
131             destination_nat: 'destination' m{static|dynamic} mapped_dst real_dst {
132             { type => $item{__PATTERN1__}, mapped_dst => $item{mapped_dst}, real_dst => $item{real_dst}, }
133             }
134             int_interface: '(' m{\w+} ',' { $item{__PATTERN1__} }
135             ext_interface: m{\w+} ')' { $item{__PATTERN1__} }
136             real_src: 'any' { $item[1] } | object_name { $item[1] }
137             mapped_src: 'any' { $item[1] } | 'interface' { $item[1] } | object_name { $item[1] }
138             mapped_dst: 'any' { $item[1] } | object_name { $item[1] }
139             real_dst: 'any' { $item[1] } | object_name { $item[1] }
140             nat_description: 'description' m{[ -~]+} { $item{__PATTERN1__} }
141             proxy_arp: 'no-proxy-arp' { 1; }
142             route_lookup: 'route-lookup' { 1; }
143              
144             object: 'object' m{network|service} object_name (?) description(?) {
145             {
146             name => $item{object_name},
147             object_type => $item{__PATTERN1__},
148             object_value => _zero_or_one($item{'$item{__PATTERN1__}_obj_body(?)'}, undef, {}),
149             description => _zero_or_one($item{'description(?)'})
150             }
151             }
152              
153             network_obj_body: 'host' ipv4 { { type => 'host', ip => $item{ipv4} } } |
154             'range' range { { type => 'range', range_start => $item{range}->[0], range_end => $item{range}->[1] } } |
155             'subnet' subnet { { type => 'subnet', network => $item{subnet}->[0], netmask => $item{subnet}->[1] } } |
156             'fqdn' fqdn { { type => 'fqdn', fqdn => $item{fqdn} } }
157              
158             service_obj_body:
159             'service' protocol { $item{protocol} }
160              
161             protocol:
162             m{\d{1,3}} { { protocol => $item{__PATTERN1__} } } |
163             m{ah|eigrp|esp|gre|igmp|igrp|ip|ipinip|ipsec|nos|ospf|pcp|pim|pptp|sctp|snp} { { protocol => $item{__PATTERN1__} } } |
164             m{tcp|udp} ('source' port_spec)(?) ('destination' port_spec)(?) {
165             my $source_spec = $item{'_alternation_1_of_production_3_of_rule_protocol(?)'};
166             my $dest_spec = $item{'_alternation_2_of_production_3_of_rule_protocol(?)'};
167              
168             {
169             protocol => $item{__PATTERN1__},
170             source => _zero_or_one($source_spec, sub { $_[0]->[0]->{port_spec} }, {}),
171             destination => _zero_or_one($dest_spec, sub { $_[0]->[0]->{port_spec} }, {}),
172             }
173             }
174              
175             port_spec:
176             m{eq|gt|lt|neq} m{\w+} { { op => $item{__PATTERN1__}, port => $item{__PATTERN2__} } } |
177             'range' m{\w+} m{\w+} { { op => $item{__STRING1__}, port_start => $item{__PATTERN1__}, port_end => $item{__PATTERN2__} } }
178              
179            
180              
181              
182             object_group:
183             'object-group' m{network|service|protocol} object_name m{(tcp|tcp-udp|udp)?}
184             description(?) (s?) {
185             {
186             name => $item{object_name},
187             group_type => $item{__PATTERN1__},
188             group_members => $item{'$item{__PATTERN1__}_obj_grp_body(s?)'},
189             description => _zero_or_one($item{'description(?)'}),
190             }
191             }
192            
193             network_obj_grp_body:
194             'group-object' object_name { { type => 'group-object', group => $item{object_name} } } |
195             'network-object host' ipv4 { { type => 'host', ip => $item{ipv4} } } |
196             'network-object' subnet { { type => 'subnet', network => $item{subnet}->[0], netmask => $item{subnet}->[1] } } |
197             'network-object object' object_name { { type => 'object', object => $item{object_name} } }
198              
199              
200             service_obj_grp_body:
201             port_object { $item{port_object} } |
202             group_object { $item{group_object} } |
203             service_object { $item{service_object} }
204              
205             port_object:
206             'port-object' 'eq' m{\w+} {
207             {
208             type => 'port-object',
209             operator => 'eq',
210             port => $item{__PATTERN1__}
211             }
212             }
213             |
214             'port-object' 'range' m{\w+} m{\w+} {
215             {
216             type => 'port-object',
217             operator => 'range',
218             port_start => $item{__PATTERN1__},
219             port_end => $item{__PATTERN2__},
220             }
221             }
222              
223             group_object: 'group-object' object_name { { type => 'group', object => $item{object_name} } }
224              
225             service_object:
226             'service-object object' object_name { { type => 'service-object', object => $item{object_name} } } |
227             'service-object' protocol { { type => 'service', %{ $item{protocol} } } }
228              
229            
230              
231             protocol_obj_grp_body:
232             group_object { $item{group_object} } |
233             protocol_object { $item{protocol_object} }
234              
235             protocol_object: 'protocol-object' m{\w+} {
236             {
237             type => 'protocol-object',
238             protocol => $item{__PATTERN1__}
239             }
240             }
241            
242            
243              
244              
245             # Utility definitions, used in many placed
246             ipv4: m{\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}} { $item{__PATTERN1__} }
247             netmask: m{\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}} { $item{__PATTERN1__} }
248             fqdn: m{[\-\w\.]+} { $item[1] }
249             range: ipv4 ipv4 { [$item[1], $item[2]] }
250             subnet: ipv4 netmask { [$item[1], $item[2]] }
251              
252             description: 'description' m{[ -~]+} { $item{__PATTERN1__} }
253              
254             name_alias: m{[!-~]+} { $item{__PATTERN1__} }
255             object_name: m{[!-~]+} { $item{__PATTERN1__} }
256              
257             interface: m{[!-~]+} { $item{__PATTERN1__} }
258              
259             alias_or_ipv4: name_alias { $item[1] } | ipv4 { $item[1] }
260              
261             unrecognised: m{\N+} {
262             { type => $item[0], config => $item[1] }
263             }
264             });
265             }
266              
267             =head2 parse_config
268              
269             For more information on the subroutine, see L.
270              
271             =cut
272              
273             sub parse_config {
274 25     25 1 253280 my ($parser, $config_contents) = @_;
275              
276             #$::RD_TRACE = 1;
277              
278 25         208 my $parse_tree = $parser->startrule($config_contents);
279              
280 25         76267 return $parse_tree;
281             }
282              
283             =head2 post_process
284              
285             For more information on the subroutine, see L.
286              
287             This module does not post-process the data structure.
288              
289              
290             =cut
291              
292             sub post_process {
293 25     25 1 132 my ($parsed_config) = @_;
294              
295 25         60 return $parsed_config;
296             }
297              
298             =head2 get_output_drivers
299              
300             For more information on the subroutine, see L.
301              
302             This module supports the following output drivers:
303              
304             =over 4
305              
306             =item * csv - writes the parsed configuration out in CSV format.
307              
308              
309             =back
310              
311             =cut
312              
313             sub get_output_drivers {
314             return {
315 0     0 1   csv => \&csv_output_driver,
316             };
317             }
318              
319              
320             =head2 csv_output_driver
321              
322             =cut
323              
324             sub csv_output_driver {
325 0     0 1   my ($fh, $filename, $parsed_config) = @_;
326 0           my $csv_type_driver = {
327             interface => \&_csv_interface_driver,
328             static_route => \&_csv_static_route_driver,
329             not_config => \&_csv_not_config_driver,
330             };
331              
332 0           say "=" x 16 . "BEGIN FILE $filename" . "=" x 16;
333              
334             TYPE:
335 0           for my $type (keys %{ $parsed_config }) {
  0            
336 0           say "-" x 8 . "BEGIN TYPE $type" . "-" x 8;
337              
338             defined $csv_type_driver->{$type} ?
339 0 0         $csv_type_driver->{$type}->($fh, $parsed_config->{$type}) :
    0          
340             warn "No CSV output driver for $type\n" and next TYPE;
341              
342 0           say "-" x 8 . "END TYPE $type" . "-" x 8;
343             }
344              
345 0           say "-" x 8 . "END FILE $filename" . "-" x 8;
346             }
347              
348             sub _csv_interface_driver {
349 0     0     my ($fh, $interfaces_ref) = @_;
350              
351             # Print the CSV schema line
352 0           my @interface_properties = qw{name state vlan ipv4_address ipv4_mask auto_negotiation link_speed mtu comment};
353 0           say $fh join(',', @interface_properties);
354              
355             # Interface through the interfaces, extract and print their properties
356 0           for my $interface (@{ $interfaces_ref }) {
  0            
357 0           my @properties = @{ $interface }{ @interface_properties };
  0            
358              
359             # Replace any undef with an empty string
360 0 0         @properties = map { defined $_ ? $_ : '' } @properties;
  0            
361 0           say $fh join(',', @properties);
362             }
363             }
364              
365              
366             sub _csv_static_route_driver {
367 0     0     my ($fh, $static_routes_ref) = @_;
368              
369 0           my @static_route_properties = qw{destination nexthop nexthop_type status};
370 0           say $fh join(',', @static_route_properties);
371              
372 0           for my $route (@{ $static_routes_ref }) {
  0            
373 0           my @properties = @{ $route }{ @static_route_properties };
  0            
374              
375             # Replace any undef with an empty string
376 0 0         @properties = map { defined $_ ? $_ : '' } @properties;
  0            
377 0           say $fh join(',', @properties);
378             }
379             }
380              
381              
382             sub _csv_not_config_driver {
383 0     0     my ($fh, $not_config) = @_;
384              
385 0           for my $config_line (@{ $not_config }) {
  0            
386 0           print $fh "$config_line\n";
387             }
388             }
389              
390              
391             =head1 AUTHOR
392              
393             Greg Foletta, C<< >>
394              
395             =head1 BUGS
396              
397             Please report any bugs or feature requests to C, or through
398             the web interface at L. I will be notified, and then you'll
399             automatically be notified of progress on your bug as I make changes.
400              
401              
402              
403              
404             =head1 SUPPORT
405              
406             You can find documentation for this module with the perldoc command.
407              
408             perldoc Device::CheckPoint::ConfigParse
409              
410              
411             You can also look for information at:
412              
413             =over 4
414              
415             =item * RT: CPAN's request tracker (report bugs here)
416              
417             L
418              
419             =item * AnnoCPAN: Annotated CPAN documentation
420              
421             L
422              
423             =item * CPAN Ratings
424              
425             L
426              
427             =item * Search CPAN
428              
429             L
430              
431             =back
432              
433              
434             =head1 ACKNOWLEDGEMENTS
435              
436              
437             =head1 LICENSE AND COPYRIGHT
438              
439             Copyright 2017 Greg Foletta.
440              
441             This program is free software; you can redistribute it and/or modify it
442             under the terms of the the Artistic License (2.0). You may obtain a
443             copy of the full license at:
444              
445             L
446              
447             Any use, modification, and distribution of the Standard or Modified
448             Versions is governed by this Artistic License. By using, modifying or
449             distributing the Package, you accept this license. Do not use, modify,
450             or distribute the Package, if you do not accept this license.
451              
452             If your Modified Version has been derived from a Modified Version made
453             by someone other than you, you are nevertheless required to ensure that
454             your Modified Version complies with the requirements of this license.
455              
456             This license does not grant you the right to use any trademark, service
457             mark, tradename, or logo of the Copyright Holder.
458              
459             This license includes the non-exclusive, worldwide, free-of-charge
460             patent license to make, have made, use, offer to sell, sell, import and
461             otherwise transfer the Package with respect to any patent claims
462             licensable by the Copyright Holder that are necessarily infringed by the
463             Package. If you institute patent litigation (including a cross-claim or
464             counterclaim) against any party alleging that the Package constitutes
465             direct or contributory patent infringement, then this Artistic License
466             to you shall terminate on the date that such litigation is filed.
467              
468             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
469             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
470             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
471             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
472             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
473             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
474             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
475             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
476              
477              
478             =cut
479              
480             1; # End of Device::CheckPoint::ConfigParse