File Coverage

blib/lib/Net/Cisco/FMC/v1.pm
Criterion Covered Total %
statement 32 295 10.8
branch 0 70 0.0
condition 0 12 0.0
subroutine 11 32 34.3
pod 16 16 100.0
total 59 425 13.8


line stmt bran cond sub pod time code
1             package Net::Cisco::FMC::v1;
2             $Net::Cisco::FMC::v1::VERSION = '0.009001';
3             # ABSTRACT: Cisco Firepower Management Center (FMC) API version 1 client library
4              
5 1     1   235022 use 5.024;
  1         9  
6 1     1   6 use utf8;
  1         1  
  1         5  
7 1     1   560 use Moo;
  1         7054  
  1         10  
8 1     1   1538 use feature 'signatures';
  1         2  
  1         157  
9 1     1   603 use Types::Standard qw( ArrayRef Dict Str );
  1         114229  
  1         11  
10 1     1   2760 use Carp qw( croak );
  1         2  
  1         67  
11 1     1   535 use Clone qw( clone );
  1         2445  
  1         58  
12 1     1   474 use Syntax::Keyword::Try;
  1         2194  
  1         6  
13 1     1   554 use Net::Cisco::FMC::v1::Role::ObjectMethods;
  1         3  
  1         36  
14 1     1   637 use JSON qw( decode_json );
  1         9825  
  1         6  
15             # use Data::Dumper::Concise;
16              
17 1     1   162 no warnings "experimental::signatures";
  1         2  
  1         4120  
18              
19              
20              
21             has 'domains' => (
22             is => 'rwp',
23             isa => ArrayRef[Dict[name => Str, uuid => Str]],
24             clearer => 1,
25             );
26              
27              
28             has 'domain_uuid' => (
29             is => 'rw',
30             clearer => 1,
31             );
32              
33             has '_refresh_token' => (
34             is => 'rw',
35             clearer => 1,
36             );
37              
38             with 'Net::Cisco::FMC::v1::Role::REST::Client';
39              
40 0     0     sub _create ($self, $url, $object_data, $query_params = {}, $expected_code = 201) {
  0            
  0            
  0            
  0            
  0            
  0            
41 0           my $params = $self->user_agent->www_form_urlencode( $query_params );
42 0           my $res = $self->post("$url?$params", $object_data);
43 0           my $code = $res->code;
44 0           my $data = $res->data;
45             croak($data->{error}->{messages}[0]->{description})
46 0 0         unless $code == $expected_code;
47 0           return $data;
48             }
49              
50 0     0     sub _list ($self, $url, $query_params = {}) {
  0            
  0            
  0            
  0            
51             # the API only allows 1000 objects at a time
52             # work around that by making multiple API calls
53 0           my $offset = 0;
54 0           my $limit = 1000;
55 0           my $more_data_available = 1;
56 0           my @items;
57 0           while ($more_data_available) {
58 0           my $res = $self->get($url, {
59             offset => $offset,
60             limit => $limit,
61             %$query_params,
62             });
63 0           my $code = $res->code;
64 0           my $data = $res->data;
65              
66             croak($data->{error}->{messages}[0]->{description})
67 0 0         unless $code == 200;
68              
69             push @items, $data->{items}->@*
70 0 0 0       if exists $data->{items} && ref $data->{items} eq 'ARRAY';
71              
72             # check if more data is available
73 0 0         if ($offset + $limit < $data->{paging}->{count}) {
74 0           $more_data_available = 1;
75 0           $offset += $limit;
76             }
77             else {
78 0           $more_data_available = 0;
79             }
80             }
81              
82             # return response similar to FMC API
83 0           return { items => \@items };
84             }
85              
86 0     0     sub _get ($self, $url, $query_params = {}) {
  0            
  0            
  0            
  0            
87 0           my $res = $self->get($url, $query_params);
88 0           my $code = $res->code;
89 0           my $data = $res->data;
90              
91             croak($data->{error}->{messages}[0]->{description})
92 0 0         unless $code == 200;
93              
94 0           return $data;
95             }
96              
97 0     0     sub _update ($self, $url, $object, $object_data, $query_params = {}) {
  0            
  0            
  0            
  0            
  0            
  0            
98 0           my $updated_data = clone($object);
99 0           delete $updated_data->{links};
100 0           delete $updated_data->{metadata};
101 0           delete $updated_data->{error};
102 0           $updated_data = { %$updated_data, %$object_data };
103              
104 0           my $params = $self->user_agent->www_form_urlencode( $query_params );
105 0           my $res = $self->put("$url?$params", $updated_data);
106 0           my $code = $res->code;
107 0           my $data = $res->data;
108             my $errmsg = ref $data eq 'HASH'
109             ? $data->{error}->{messages}[0]->{description}
110 0 0         : $data;
111 0 0         croak($errmsg)
112             unless $code == 200;
113              
114 0           return $data;
115             }
116              
117 0     0     sub _delete ($self, $url) {
  0            
  0            
  0            
118 0           my $res = $self->delete($url);
119             croak($res->data->{error}->{messages}[0]->{description})
120 0 0         unless $res->code == 200;
121 0           return 1;
122             }
123              
124             Net::Cisco::FMC::v1::Role::ObjectMethods->apply([
125             {
126             path => 'object',
127             object => 'portobjectgroups',
128             singular => 'portobjectgroup',
129             },
130             {
131             path => 'object',
132             object => 'protocolportobjects',
133             singular => 'protocolportobject',
134             },
135             {
136             path => 'object',
137             object => 'icmpv4objects',
138             singular => 'icmpv4object',
139             },
140             {
141             path => 'object',
142             object => 'icmpv6objects',
143             singular => 'icmpv6object',
144             },
145             {
146             path => 'object',
147             object => 'interfacegroups',
148             singular => 'interfacegroup',
149             },
150             {
151             path => 'object',
152             object => 'networkgroups',
153             singular => 'networkgroup',
154             },
155             {
156             path => 'object',
157             object => 'networks',
158             singular => 'network',
159             },
160             {
161             path => 'object',
162             object => 'hosts',
163             singular => 'host',
164             },
165             {
166             path => 'object',
167             object => 'ranges',
168             singular => 'range',
169             },
170             {
171             path => 'object',
172             object => 'fqdns',
173             singular => 'fqdn',
174             },
175             {
176             path => 'object',
177             object => 'securityzones',
178             singular => 'securityzone',
179             },
180             {
181             path => 'object',
182             object => 'slamonitors',
183             singular => 'slamonitor',
184             },
185             {
186             path => 'object',
187             object => 'urlgroups',
188             singular => 'urlgroup',
189             },
190             {
191             path => 'object',
192             object => 'urls',
193             singular => 'url',
194             },
195             {
196             path => 'object',
197             object => 'vlangrouptags',
198             singular => 'vlangrouptag',
199             },
200             {
201             path => 'object',
202             object => 'vlantags',
203             singular => 'vlantag',
204             },
205             {
206             path => 'policy',
207             object => 'accesspolicies',
208             singular => 'accesspolicy',
209             },
210             {
211             path => 'object',
212             object => 'networkaddresses',
213             singular => 'networkaddress',
214             },
215             {
216             path => 'object',
217             object => 'ports',
218             singular => 'port',
219             },
220             {
221             path => 'devices',
222             object => 'devicerecords',
223             singular => 'devicerecord',
224             },
225             {
226             path => 'assignment',
227             object => 'policyassignments',
228             singular => 'policyassignment',
229             },
230             {
231             path => 'object',
232             object => 'realms',
233             singular => 'realm',
234             },
235             {
236             path => 'object',
237             object => 'realmusers',
238             singular => 'realmuser',
239             },
240             {
241             path => 'object',
242             object => 'realmusergroups',
243             singular => 'realmusergroup',
244             },
245             {
246             path => 'policy',
247             object => 'identitypolicies',
248             singular => 'identitypolicy',
249             },
250             ]);
251              
252              
253 0     0 1   sub login($self) {
  0            
  0            
254 0           my $res = $self->post('/api/fmc_platform/v1/auth/generatetoken', undef,
255             { authentication => 'basic' });
256 0 0         if ($res->code == 204) {
257             # the allowed domains are returned in the domains header JSON
258             # encoded
259 0           my $domains = decode_json($res->response->header('domains'));
260             #say Dumper($domains);
261 0           $self->_set_domains($domains);
262             # set the current domain to the first available
263 0           $self->domain_uuid($domains->[0]->{uuid});
264              
265             # store refresh token
266 0           $self->_refresh_token($res->response->header('x-auth-refresh-token'));
267 0           $self->set_persistent_header('X-auth-access-token',
268             $res->response->header('x-auth-access-token'));
269             }
270             else {
271 0           croak($res->data->{error}->{messages}[0]->{description});
272             }
273             }
274              
275              
276 0     0 1   sub relogin($self) {
  0            
  0            
277 0           my $domain_uuid = $self->domain_uuid;
278 0           $self->login;
279 0 0 0       $self->domain_uuid($domain_uuid)
280             if defined $domain_uuid && $domain_uuid ne '';
281             }
282              
283              
284 0     0 1   sub logout($self) {
  0            
  0            
285 0           my $res = $self->post('/api/fmc_platform/v1/auth/revokeaccess');
286 0 0         if ($res->code == 204) {
287 0           $self->clear_domains;
288 0           $self->clear_domain_uuid;
289 0           $self->_clear_refresh_token;
290 0           $self->clear_persistent_headers;
291             }
292             else {
293 0           croak($res->data->{error}->{messages}[0]->{description});
294             }
295             }
296              
297              
298 0     0 1   sub create_accessrule ($self, $accesspolicy_id, $object_data, $query_params = {}) {
  0            
  0            
  0            
  0            
  0            
299 0           return $self->_create(join('/',
300             '/api/fmc_config/v1/domain',
301             $self->domain_uuid,
302             'policy',
303             'accesspolicies',
304             $accesspolicy_id,
305             'accessrules'
306             ), $object_data, $query_params);
307             }
308              
309              
310 0     0 1   sub list_accessrules ($self, $accesspolicy_id, $query_params = {}) {
  0            
  0            
  0            
  0            
311 0           return $self->_list(join('/',
312             '/api/fmc_config/v1/domain',
313             $self->domain_uuid,
314             'policy',
315             'accesspolicies',
316             $accesspolicy_id,
317             'accessrules'
318             ), $query_params);
319             }
320              
321              
322 0     0 1   sub get_accessrule ($self, $accesspolicy_id, $id, $query_params = {}) {
  0            
  0            
  0            
  0            
  0            
323 0           return $self->_get(join('/',
324             '/api/fmc_config/v1/domain',
325             $self->domain_uuid,
326             'policy',
327             'accesspolicies',
328             $accesspolicy_id,
329             'accessrules',
330             $id
331             ), $query_params);
332             }
333              
334              
335 0     0 1   sub update_accessrule ($self, $accesspolicy_id, $object, $object_data, $query_params = {}) {
  0            
  0            
  0            
  0            
  0            
  0            
336 0           my $id = $object->{id};
337 0           return $self->_update(join('/',
338             '/api/fmc_config/v1/domain',
339             $self->domain_uuid,
340             'policy',
341             'accesspolicies',
342             $accesspolicy_id,
343             'accessrules',
344             $id
345             ), $object, $object_data, $query_params);
346             }
347              
348              
349              
350 0     0 1   sub delete_accessrule ($self, $accesspolicy_id, $id) {
  0            
  0            
  0            
  0            
351 0           return $self->_delete(join('/',
352             '/api/fmc_config/v1/domain',
353             $self->domain_uuid,
354             'policy',
355             'accesspolicies',
356             $accesspolicy_id,
357             'accessrules',
358             $id
359             ));
360             }
361              
362              
363 0     0 1   sub list_deployabledevices ($self, $query_params = {}) {
  0            
  0            
  0            
364 0           return $self->_list(join('/',
365             '/api/fmc_config/v1/domain',
366             $self->domain_uuid,
367             'deployment',
368             'deployabledevices'
369             ), $query_params);
370             }
371              
372              
373 0     0 1   sub create_deploymentrequest ($self, $object_data) {
  0            
  0            
  0            
374 0           my $data = $self->_create(join('/',
375             '/api/fmc_config/v1/domain',
376             $self->domain_uuid,
377             'deployment',
378             'deploymentrequests'
379             ), $object_data, {}, 202);
380 0           return $data;
381             }
382              
383              
384 0     0 1   sub get_task ($self, $id) {
  0            
  0            
  0            
385 0           return $self->_get(join('/',
386             '/api/fmc_config/v1/domain',
387             $self->domain_uuid,
388             'job',
389             'taskstatuses',
390             $id
391             ));
392             }
393              
394              
395 0     0 1   sub wait_for_task ($self, $id, $callback) {
  0            
  0            
  0            
  0            
396 0 0         croak "id missing"
397             unless defined $id;
398 0 0 0       croak "callback must be a coderef"
399             if defined $callback && ref $callback ne 'CODE';
400              
401 0           my %in_progress_status_for_type = (
402             DEVICE_DEPLOYMENT => 'Deploying',
403             );
404              
405 0           my $task = $self->get_task($id);
406             die "support for task type '$task->{taskType}' not implemented\n"
407 0 0         unless exists $in_progress_status_for_type{$task->{taskType}};
408             do {
409 0 0         &$callback($task)
410             if defined $callback;
411 0           sleep 1;
412 0           $task = $self->get_task($id);
413             } until (
414 0           $task->{status} ne $in_progress_status_for_type{$task->{taskType}});
415 0           return $task;
416             }
417              
418              
419 0     0 1   sub cleanup_protocolport ($self, $portobj) {
  0            
  0            
  0            
420             #say Dumper($rule);
421             #say "protocolport: " . Dumper($portobj);
422 0           my $protocolportobject = $self->get_protocolportobject($portobj->{id});
423             #say Dumper($protocolportobject);
424 0           my $new_name = lc($protocolportobject->{protocol});
425 0 0         if ( exists $protocolportobject->{port} ) {
426 0           $new_name .= '_' . $protocolportobject->{port};
427             }
428             # avoid 'predefined name' errors
429             else {
430 0           $new_name .= '_any';
431             }
432              
433 0           say "\t", $protocolportobject->{name}, ' ⮕ ', $new_name;
434             try {
435             my $portobject = $self->update_protocolportobject($protocolportobject, { name => $new_name });
436             say "\tname updated";
437             return { %$portobject{qw( id type )} };
438             }
439 0           catch {
440             # replace with existing object
441             if ( $@ =~ /The object name \S+ already exists/ ) {
442             # find existing object
443             my $existing_portobject = $self->find_protocolportobject({ name => $new_name });
444             say "\texisting object used";
445             return { %$existing_portobject{qw( id type )} };
446             }
447             else {
448             croak "name update failed: $@";
449             }
450             }
451             }
452              
453              
454 0     0 1   sub cleanup_icmpv4object ($self, $icmpv4obj) {
  0            
  0            
  0            
455             #say "icmpv4object: " . Dumper($icmpv4obj);
456 0           my $icmpv4object = $self->get_icmpv4object($icmpv4obj->{id});
457             #say Dumper($icmpv4object);
458 0           my $new_name = 'icmp_' . lc($icmpv4object->{icmpType});
459             $new_name .= '_' . $icmpv4object->{code}
460 0 0         if exists $icmpv4object->{code};
461              
462 0           say "\t", $icmpv4object->{name}, ' ⮕ ', $new_name;
463             try {
464             my $obj = $self->update_icmpv4object($icmpv4object, { name => $new_name });
465             say "\tname updated";
466             return { %$obj{qw( id type )} };
467             }
468 0           catch {
469             #say "name update failed: $@";
470             # replace with existing object
471             if ( $@ =~ /The object name \S+ already exists/ ) {
472             # find existing object
473             my $existing_object = $self->find_icmpv4object({ name => $new_name });
474             say "\texisting object used";
475             return { %$existing_object{qw( id type )} };
476             }
477             elsif ( $@ =~ /conflicts with predefined name on device/ ) {
478             say "\t$@";
479             }
480             else {
481             croak "name update failed: $@";
482             }
483             }
484             }
485              
486              
487 0     0 1   sub cleanup_hosts($self) {
  0            
  0            
488 0           for my $object ($self->list_hosts({ expanded => 'true' })->{items}->@*) {
489             try {
490             #say $object->{name};
491             #say Dumper($object);
492             if ($object->{name} =~ /^(.*)_Mask32$/) {
493             my $new_name = $1;
494             say 'renaming host ', $object->{name}, ' ⮕ ', $new_name;
495             $self->update_host($object, { name => $new_name });
496             }
497             # clear description
498             if ($object->{description} eq 'Created during ASA Migration') {
499             $self->update_host($object, { description => '' });
500             }
501             }
502 0           catch {
503             warn $@;
504             }
505             }
506             }
507              
508              
509             sub create_cleaned_accesspolicy (
510 0           $self,
511 0           $source_accesspolicy_name,
512 0     0 1   $optional = {}) {
  0            
  0            
513             my $destination_accesspolicy_name = exists $optional->{target_access_policy_name}
514             ? $optional->{target_access_policy_name}
515 0 0         : $source_accesspolicy_name . '-cleaned';
516              
517             my @accesspolicies = $self->list_accesspolicies({ expanded => 'true'
518 0           })->{items}->@*;
519 0           for my $accesspolicy (@accesspolicies) {
520             next
521 0 0         unless $accesspolicy->{name} eq $source_accesspolicy_name;
522 0           say "cleaning " . $accesspolicy->{id}, ': ', $accesspolicy->{name};
523             #say Dumper($accesspolicy);
524             #say "creating new accesspolicy: " . Dumper($accesspolicy);
525              
526             # check if the cleaned accesspolicy already exists, in that case
527             # resume
528 0           my $new_accesspolicy;
529 0           for my $accesspolicy (@accesspolicies) {
530 0 0         if ($accesspolicy->{name} eq $destination_accesspolicy_name) {
531 0           $new_accesspolicy = $accesspolicy;
532 0           last;
533             }
534             }
535              
536 0           my $resume_rulenumber;
537 0 0         if (defined $new_accesspolicy) {
538             # find first rule to resume cleanup
539             my @rules = $self->list_accessrules($new_accesspolicy->{id})
540 0           ->{items}->@*;
541 0           $resume_rulenumber = scalar @rules + 1;
542 0           say "resuming cleanup of $destination_accesspolicy_name ",
543             "at rule #$resume_rulenumber\n";
544             }
545             else {
546 0           $new_accesspolicy = $self->create_accesspolicy({
547             name => $destination_accesspolicy_name,
548             defaultAction => {
549             action => 'BLOCK',
550             logBegin => 1,
551             sendEventsToFMC => 1,
552             },
553             });
554             }
555             #say Dumper($new_accesspolicy);
556              
557 0           my $rulenumber = 1;
558 0           RULE: for my $rule ($self->list_accessrules($accesspolicy->{id},
559             { expanded => 'true' })->{items}->@*) {
560 0 0 0       if ( defined $resume_rulenumber
561             && $rulenumber < $resume_rulenumber ) {
562 0           $rulenumber++;
563 0           next RULE;
564             }
565              
566             #next RULE
567             # unless $rule->{name} eq 'outside_access_in#15-1';
568 0           say $rule->{name};
569             # copy all attributes of the existing rule to the new one
570 0           my $updated_data = clone($rule);
571             # remove attributes that are not needed/allowed in the create
572             # call
573 0           delete $updated_data->{id};
574 0           delete $updated_data->{links};
575 0           delete $updated_data->{metadata};
576 0           delete $updated_data->{commentHistoryList};
577             #my $rule_for_diff = clone($updated_data);
578              
579 0 0         if (exists $optional->{rule_name_coderef}) {
580 0           $updated_data->{name} = $optional->{rule_name_coderef}->($rulenumber, $rule);
581             }
582              
583 0           $rulenumber++;
584              
585 0           for my $networktype (qw( sourceNetworks destinationNetworks )) {
586 0           my $src_networks = $rule->{$networktype};
587 0           for my $key (keys $src_networks->%*) {
588 0 0         if ($key eq 'objects') {
589 0           $updated_data->{$networktype}->{objects} = [];
590             #say "old: " . Dumper($src_networks->{objects});
591 0           for my $network ($src_networks->{objects}->@*) {
592             #say Dumper($network);
593 0           my $name = $network->{name};
594 0           my $type = $network->{type};
595 0 0         if ( $name =~ /^DM_INLINE_/ ) {
596             # eliminate autogenerated NetworkGroups
597 0 0         if ( $type eq 'NetworkGroup' ) {
598 0           my $networkgroup = $self->get_networkgroup($network->{id});
599             my $object_count =
600             (exists $networkgroup->{objects}
601             ? scalar $networkgroup->{objects}->@*
602             : 0)
603             + (exists $networkgroup->{literals}
604             ? scalar $networkgroup->{literals}->@*
605 0 0         : 0);
    0          
606 0 0         if ( $object_count > 50 ) {
607 0           warn "\tnumber of objects (",
608             $object_count, ") would exceed ",
609             "current FMC limit of 50, ",
610             "keeping current contents\n";
611             }
612             else {
613 0           say "\tmoving contents of group $name directly into rule";
614 0           for my $objecttype (qw( objects literals )) {
615 0           for my $networkobject ($networkgroup->{$objecttype}->@*) {
616             #say Dumper($networkobject);
617 0           push $updated_data->{$networktype}->{$objecttype}->@*, $networkobject;
618             }
619             }
620             }
621             }
622             else {
623 0           warn "object type $type not supported, keeping original object!";
624 0           push $updated_data->{$networktype}->{objects}->@*, $network;
625             }
626             }
627             # keep non-autogenerated objects
628             else {
629 0           push $updated_data->{$networktype}->{objects}->@*, $network;
630             }
631             }
632             }
633             # copy all other contents
634             else {
635 0           $updated_data->{$networktype}->{$key} = $src_networks->{$key};
636             }
637             }
638             }
639              
640 0           my $ports = $rule->{destinationPorts};
641 0 0         if (exists $ports->{objects} ) {
642             $updated_data->{destinationPorts} = {
643 0           objects => []
644             };
645             #say "old: " . Dumper($ports->{objects});
646 0           for my $portobj ($ports->{objects}->@*) {
647             #say Dumper($portobj);
648 0           my $name = $portobj->{name};
649 0           my $type = $portobj->{type};
650              
651 0 0         if ( $name =~ /^DM_INLINE_/ ) {
652             # eliminate autogenerated PortObjectGroups
653 0 0         if ( $type eq 'ProtocolPortObject' ) {
    0          
    0          
654             push
655             $updated_data->{destinationPorts}->{objects}->@*,
656             $self->cleanup_protocolport({
657 0           %$portobj{qw( id type )} });
658             }
659             elsif ( $type eq 'ICMPV4Object' ) {
660             push
661             $updated_data->{destinationPorts}->{objects}->@*,
662             $self->cleanup_icmpv4object({
663 0           %$portobj{qw( id type )} });
664             }
665             elsif ( $type eq 'PortObjectGroup' ) {
666 0           say "\tmoving contents of group $name directly into rule";
667             #say Dumper($rule);
668 0           my $portobjectgroup = $self->get_portobjectgroup($portobj->{id});
669 0           for my $portobject ($portobjectgroup->{objects}->@*) {
670             #say Dumper($portobject);
671             my $object = $portobject->{type} eq
672             'ProtocolPortObject'
673             ? $self->cleanup_protocolport(
674             {%$portobject{qw( id type )}}
675             )
676 0 0         : {%$portobject{qw( id type )}};
677             push $updated_data->{destinationPorts}->{objects}->@*,
678 0           $object;
679             }
680             }
681             else {
682 0           warn "unhandled object type $type, keeping unmodified\n";
683 0           push $updated_data->{destinationPorts}->{objects}->@*, $portobj;
684             }
685              
686             #my $protocolportobject = $self->get_protocolportobject($portobj->{id});
687             #say Dumper($protocolportobject);
688             }
689             # keep non-autogenerated objects
690             else {
691 0           push $updated_data->{destinationPorts}->{objects}->@*, $portobj;
692             }
693             }
694             }
695             #say "new: " . Dumper($updated_data);
696             # always replace existing destinationPorts because one of
697             # them might have been replaced with an existing one
698             # FIXME: check if literals get lost
699             #$self->update_accessrule($accesspolicy->{id}, $rule, $updated_data)
700             # if $updated_data->%*;
701             #say Dumper($updated_data);
702             #use Test::Differences;
703             #eq_or_diff($updated_data, $rule_for_diff, 'rule');
704 0           $self->create_accessrule($new_accesspolicy->{id}, $updated_data);
705             #last RULE;
706             }
707             }
708             }
709              
710             1;
711              
712             __END__
713              
714             =pod
715              
716             =encoding UTF-8
717              
718             =head1 NAME
719              
720             Net::Cisco::FMC::v1 - Cisco Firepower Management Center (FMC) API version 1 client library
721              
722             =head1 VERSION
723              
724             version 0.009001
725              
726             =head1 SYNOPSIS
727              
728             use strict;
729             use warnings;
730             use Net::Cisco::FMC::v1;
731             use Data::Dumper::Concise;
732              
733             my $fmc = Net::Cisco::FMC::v1->new(
734             server => 'https://fmcrestapisandbox.cisco.com',
735             user => 'admin',
736             passwd => '$password',
737             clientattrs => { timeout => 30 },
738             );
739              
740             # login to populate domains
741             $fmc->login;
742              
743             # list all domain uuids and names
744             print Dumper($fmc->domains);
745             # switch domain
746             $fmc->domain_uuid("e276abec-e0f2-11e3-8169-6d9ed49b625f");
747              
748             =head1 DESCRIPTION
749              
750             This module is a client library for the Cisco Firepower Management
751             Center (FMC) REST API version 1.
752             Currently it is developed and tested against FMC version 7.2.0.1.
753              
754             =head1 ATTRIBUTES
755              
756             =head2 domains
757              
758             Returns a list of hashrefs containing name and uuid of all domains which gets
759             populated by L</login>.
760              
761             =head2 domain_uuid
762              
763             The UUID of the domain which is used by all methods.
764              
765             =head1 METHODS
766              
767             =head2 login
768              
769             Logs into the FMC by fetching an authentication token via http basic
770             authentication.
771              
772             =head2 relogin
773              
774             Refreshes the session by loging in again (not using the refresh token) and
775             restores the currently set domain_uuid.
776              
777             =head2 logout
778              
779             Logs out of the FMC.
780              
781             =head2 create_accessrule
782              
783             Takes an access policy id, a hashref of the rule which should be created and
784             optional query parameters.
785              
786             =head2 list_accessrules
787              
788             Takes an access policy id and query parameters and returns a hashref with a
789             single key 'items' that has a list of access rules similar to the FMC API.
790              
791             =head2 get_accessrule
792              
793             Takes an access policy id, rule id and query parameters and returns the access
794             rule.
795              
796             =head2 update_accessrule
797              
798             Takes an access policy id, rule object, a hashref of the rule and an optional
799             hashref of query parameters and returns a hashref of the updated access rule.
800              
801             =head2 delete_accessrule
802              
803             Takes an access policy id and a rule object id.
804              
805             Returns true on success.
806              
807             =head2 list_deployabledevices
808              
809             Takes optional query parameters and returns a hashref with a
810             single key 'items' that has a list of deployable devices similar to the FMC
811             API.
812              
813             =head2 create_deploymentrequest
814              
815             Takes a hashref of deployment parameters.
816              
817             Returns the created task in the ->{metadata}->{task} hashref.
818              
819             =head2 get_task
820              
821             Takes a task id and returns its status.
822              
823             =head2 wait_for_task
824              
825             Takes a task id and an optional callback and checks its status every second
826             until it isn't in-progress any more.
827             The in-progress status is different for each task type, currently only
828             'DEVICE_DEPLOYMENT' is supported.
829             The callback coderef which is called for every check with the task as argument.
830              
831             Returns the task.
832              
833             =head2 cleanup_protocolport
834              
835             Takes a ProtocolPortObject and renames it to protocol_port, e.g. tcp_443.
836             If it has no port 'any' is used instead of the port number no avoid
837             'predefined name' errors.
838             Returns the ProtocolPortObject with the updated attributes.
839              
840             =head2 cleanup_icmpv4object
841              
842             Takes a ICMPv4Object and renames it to protocol_type[_code], e.g. icmp_8_0.
843             If it has no code only protocol and type is used.
844              
845             =head2 cleanup_hosts
846              
847             =over
848              
849             =item removes '_Mask32' from the name
850              
851             =item removes the description if it is 'Created during ASA Migration'
852              
853             =back
854              
855             =head2 create_cleaned_accesspolicy
856              
857             Takes an access policy name and a hashref of optional arguments.
858              
859             =head3 Optional arguments
860              
861             =over
862              
863             =item target_access_policy_name
864              
865             Defaults to access policy name with the postfix '-cleaned'.
866              
867             =item rule_name_coderef
868              
869             Gets passed the rule number and rule object and must return the new rule name.
870              
871             =back
872              
873             Creates a new access policy with the target name containing all rules of the
874             input access policy but cleaned by the following rules:
875              
876             =over
877              
878             =item the commentHistoryList is omitted
879              
880             =item replace autogenerated DM_INLINE_ NetworkGroups by their content
881              
882             Only if they don't contain more than 50 items because of the current limit in
883             FMC.
884              
885             =item replace autogenerated DM_INLINE_ PortObjectGroups by their content
886              
887             =item optional: the rule name is generated
888              
889             By passing a coderef named 'rule_name_coderef' in the optional arguments
890             hashref.
891              
892             =back
893              
894             The new access policy is created with a defaultAction of:
895              
896             action => 'BLOCK'
897             logBegin => true
898             sendEventsToFMC => true
899              
900             This is mainly for access policies migrated by the Cisco Firepower Migration
901             Tool from a Cisco ASA.
902              
903             Supports resuming.
904              
905             =head1 KNOWN BUGS
906              
907             Older FMC versions have bugs like:
908              
909             =over
910              
911             =item truncated JSON responses
912              
913             No workaround on client side possible, only a FMC update helps.
914              
915             =item no response to the 11th call (version 6.2.2.1)
916              
917             No workaround on client side because newer FMC versions (at least 6.2.3.6)
918             throttle the login call too.
919              
920             =item accessrule is created but error 'You do not have the required
921             authorization to do this operation' is thrown (version 6.2.2)
922              
923             No workaround on client side possible, only a FMC update helps.
924              
925             =back
926              
927             =head1 AUTHOR
928              
929             Alexander Hartmaier <abraxxa@cpan.org>
930              
931             =head1 COPYRIGHT AND LICENSE
932              
933             This software is copyright (c) 2018 - 2023 by Alexander Hartmaier.
934              
935             This is free software; you can redistribute it and/or modify it under
936             the same terms as the Perl 5 programming language system itself.
937              
938             =cut