File Coverage

blib/lib/Device/Network/ConfigParser/Cisco/ASA.pm
Criterion Covered Total %
statement 29 65 44.6
branch 0 8 0.0
condition n/a
subroutine 11 17 64.7
pod 6 6 100.0
total 46 96 47.9


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