File Coverage

blib/lib/Net/Cisco/FMC/v1.pm
Criterion Covered Total %
statement 32 296 10.8
branch 0 72 0.0
condition 0 12 0.0
subroutine 11 32 34.3
pod 16 16 100.0
total 59 428 13.7


line stmt bran cond sub pod time code
1             package Net::Cisco::FMC::v1;
2             $Net::Cisco::FMC::v1::VERSION = '0.009002';
3             # ABSTRACT: Cisco Firepower Management Center (FMC) API version 1 client library
4              
5 1     1   241767 use 5.024;
  1         12  
6 1     1   5 use utf8;
  1         2  
  1         5  
7 1     1   590 use Moo;
  1         7625  
  1         5  
8 1     1   1499 use feature 'signatures';
  1         3  
  1         163  
9 1     1   654 use Types::Standard qw( ArrayRef Dict Str );
  1         118556  
  1         11  
10 1     1   2745 use Carp qw( croak );
  1         3  
  1         56  
11 1     1   554 use Clone qw( clone );
  1         2412  
  1         60  
12 1     1   494 use Syntax::Keyword::Try;
  1         2338  
  1         6  
13 1     1   563 use Net::Cisco::FMC::v1::Role::ObjectMethods;
  1         3  
  1         38  
14 1     1   662 use JSON qw( decode_json );
  1         10253  
  1         7  
15             # use Data::Dumper::Concise;
16              
17 1     1   156 no warnings "experimental::signatures";
  1         2  
  1         4241  
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             my $errmsg = ref $res->data eq 'HASH'
272             ? $res->data->{error}->{messages}[0]->{description}
273 0 0         : $res->data;
274 0           croak($errmsg);
275             }
276             }
277              
278              
279 0     0 1   sub relogin($self) {
  0            
  0            
280 0           my $domain_uuid = $self->domain_uuid;
281 0           $self->login;
282 0 0 0       $self->domain_uuid($domain_uuid)
283             if defined $domain_uuid && $domain_uuid ne '';
284             }
285              
286              
287 0     0 1   sub logout($self) {
  0            
  0            
288 0           my $res = $self->post('/api/fmc_platform/v1/auth/revokeaccess');
289 0 0         if ($res->code == 204) {
290 0           $self->clear_domains;
291 0           $self->clear_domain_uuid;
292 0           $self->_clear_refresh_token;
293 0           $self->clear_persistent_headers;
294             }
295             else {
296 0           croak($res->data->{error}->{messages}[0]->{description});
297             }
298             }
299              
300              
301 0     0 1   sub create_accessrule ($self, $accesspolicy_id, $object_data, $query_params = {}) {
  0            
  0            
  0            
  0            
  0            
302 0           return $self->_create(join('/',
303             '/api/fmc_config/v1/domain',
304             $self->domain_uuid,
305             'policy',
306             'accesspolicies',
307             $accesspolicy_id,
308             'accessrules'
309             ), $object_data, $query_params);
310             }
311              
312              
313 0     0 1   sub list_accessrules ($self, $accesspolicy_id, $query_params = {}) {
  0            
  0            
  0            
  0            
314 0           return $self->_list(join('/',
315             '/api/fmc_config/v1/domain',
316             $self->domain_uuid,
317             'policy',
318             'accesspolicies',
319             $accesspolicy_id,
320             'accessrules'
321             ), $query_params);
322             }
323              
324              
325 0     0 1   sub get_accessrule ($self, $accesspolicy_id, $id, $query_params = {}) {
  0            
  0            
  0            
  0            
  0            
326 0           return $self->_get(join('/',
327             '/api/fmc_config/v1/domain',
328             $self->domain_uuid,
329             'policy',
330             'accesspolicies',
331             $accesspolicy_id,
332             'accessrules',
333             $id
334             ), $query_params);
335             }
336              
337              
338 0     0 1   sub update_accessrule ($self, $accesspolicy_id, $object, $object_data, $query_params = {}) {
  0            
  0            
  0            
  0            
  0            
  0            
339 0           my $id = $object->{id};
340 0           return $self->_update(join('/',
341             '/api/fmc_config/v1/domain',
342             $self->domain_uuid,
343             'policy',
344             'accesspolicies',
345             $accesspolicy_id,
346             'accessrules',
347             $id
348             ), $object, $object_data, $query_params);
349             }
350              
351              
352              
353 0     0 1   sub delete_accessrule ($self, $accesspolicy_id, $id) {
  0            
  0            
  0            
  0            
354 0           return $self->_delete(join('/',
355             '/api/fmc_config/v1/domain',
356             $self->domain_uuid,
357             'policy',
358             'accesspolicies',
359             $accesspolicy_id,
360             'accessrules',
361             $id
362             ));
363             }
364              
365              
366 0     0 1   sub list_deployabledevices ($self, $query_params = {}) {
  0            
  0            
  0            
367 0           return $self->_list(join('/',
368             '/api/fmc_config/v1/domain',
369             $self->domain_uuid,
370             'deployment',
371             'deployabledevices'
372             ), $query_params);
373             }
374              
375              
376 0     0 1   sub create_deploymentrequest ($self, $object_data) {
  0            
  0            
  0            
377 0           my $data = $self->_create(join('/',
378             '/api/fmc_config/v1/domain',
379             $self->domain_uuid,
380             'deployment',
381             'deploymentrequests'
382             ), $object_data, {}, 202);
383 0           return $data;
384             }
385              
386              
387 0     0 1   sub get_task ($self, $id) {
  0            
  0            
  0            
388 0           return $self->_get(join('/',
389             '/api/fmc_config/v1/domain',
390             $self->domain_uuid,
391             'job',
392             'taskstatuses',
393             $id
394             ));
395             }
396              
397              
398 0     0 1   sub wait_for_task ($self, $id, $callback) {
  0            
  0            
  0            
  0            
399 0 0         croak "id missing"
400             unless defined $id;
401 0 0 0       croak "callback must be a coderef"
402             if defined $callback && ref $callback ne 'CODE';
403              
404 0           my %in_progress_status_for_type = (
405             DEVICE_DEPLOYMENT => 'Deploying',
406             );
407              
408 0           my $task = $self->get_task($id);
409             die "support for task type '$task->{taskType}' not implemented\n"
410 0 0         unless exists $in_progress_status_for_type{$task->{taskType}};
411             do {
412 0 0         &$callback($task)
413             if defined $callback;
414 0           sleep 1;
415 0           $task = $self->get_task($id);
416             } until (
417 0           $task->{status} ne $in_progress_status_for_type{$task->{taskType}});
418 0           return $task;
419             }
420              
421              
422 0     0 1   sub cleanup_protocolport ($self, $portobj) {
  0            
  0            
  0            
423             #say Dumper($rule);
424             #say "protocolport: " . Dumper($portobj);
425 0           my $protocolportobject = $self->get_protocolportobject($portobj->{id});
426             #say Dumper($protocolportobject);
427 0           my $new_name = lc($protocolportobject->{protocol});
428 0 0         if ( exists $protocolportobject->{port} ) {
429 0           $new_name .= '_' . $protocolportobject->{port};
430             }
431             # avoid 'predefined name' errors
432             else {
433 0           $new_name .= '_any';
434             }
435              
436 0           say "\t", $protocolportobject->{name}, ' ⮕ ', $new_name;
437             try {
438             my $portobject = $self->update_protocolportobject($protocolportobject, { name => $new_name });
439             say "\tname updated";
440             return { %$portobject{qw( id type )} };
441             }
442 0           catch {
443             # replace with existing object
444             if ( $@ =~ /The object name \S+ already exists/ ) {
445             # find existing object
446             my $existing_portobject = $self->find_protocolportobject({ name => $new_name });
447             say "\texisting object used";
448             return { %$existing_portobject{qw( id type )} };
449             }
450             else {
451             croak "name update failed: $@";
452             }
453             }
454             }
455              
456              
457 0     0 1   sub cleanup_icmpv4object ($self, $icmpv4obj) {
  0            
  0            
  0            
458             #say "icmpv4object: " . Dumper($icmpv4obj);
459 0           my $icmpv4object = $self->get_icmpv4object($icmpv4obj->{id});
460             #say Dumper($icmpv4object);
461 0           my $new_name = 'icmp_' . lc($icmpv4object->{icmpType});
462             $new_name .= '_' . $icmpv4object->{code}
463 0 0         if exists $icmpv4object->{code};
464              
465 0           say "\t", $icmpv4object->{name}, ' ⮕ ', $new_name;
466             try {
467             my $obj = $self->update_icmpv4object($icmpv4object, { name => $new_name });
468             say "\tname updated";
469             return { %$obj{qw( id type )} };
470             }
471 0           catch {
472             #say "name update failed: $@";
473             # replace with existing object
474             if ( $@ =~ /The object name \S+ already exists/ ) {
475             # find existing object
476             my $existing_object = $self->find_icmpv4object({ name => $new_name });
477             say "\texisting object used";
478             return { %$existing_object{qw( id type )} };
479             }
480             elsif ( $@ =~ /conflicts with predefined name on device/ ) {
481             say "\t$@";
482             }
483             else {
484             croak "name update failed: $@";
485             }
486             }
487             }
488              
489              
490 0     0 1   sub cleanup_hosts($self) {
  0            
  0            
491 0           for my $object ($self->list_hosts({ expanded => 'true' })->{items}->@*) {
492             try {
493             #say $object->{name};
494             #say Dumper($object);
495             if ($object->{name} =~ /^(.*)_Mask32$/) {
496             my $new_name = $1;
497             say 'renaming host ', $object->{name}, ' ⮕ ', $new_name;
498             $self->update_host($object, { name => $new_name });
499             }
500             # clear description
501             if ($object->{description} eq 'Created during ASA Migration') {
502             $self->update_host($object, { description => '' });
503             }
504             }
505 0           catch {
506             warn $@;
507             }
508             }
509             }
510              
511              
512             sub create_cleaned_accesspolicy (
513 0           $self,
514 0           $source_accesspolicy_name,
515 0     0 1   $optional = {}) {
  0            
  0            
516             my $destination_accesspolicy_name = exists $optional->{target_access_policy_name}
517             ? $optional->{target_access_policy_name}
518 0 0         : $source_accesspolicy_name . '-cleaned';
519              
520             my @accesspolicies = $self->list_accesspolicies({ expanded => 'true'
521 0           })->{items}->@*;
522 0           for my $accesspolicy (@accesspolicies) {
523             next
524 0 0         unless $accesspolicy->{name} eq $source_accesspolicy_name;
525 0           say "cleaning " . $accesspolicy->{id}, ': ', $accesspolicy->{name};
526             #say Dumper($accesspolicy);
527             #say "creating new accesspolicy: " . Dumper($accesspolicy);
528              
529             # check if the cleaned accesspolicy already exists, in that case
530             # resume
531 0           my $new_accesspolicy;
532 0           for my $accesspolicy (@accesspolicies) {
533 0 0         if ($accesspolicy->{name} eq $destination_accesspolicy_name) {
534 0           $new_accesspolicy = $accesspolicy;
535 0           last;
536             }
537             }
538              
539 0           my $resume_rulenumber;
540 0 0         if (defined $new_accesspolicy) {
541             # find first rule to resume cleanup
542             my @rules = $self->list_accessrules($new_accesspolicy->{id})
543 0           ->{items}->@*;
544 0           $resume_rulenumber = scalar @rules + 1;
545 0           say "resuming cleanup of $destination_accesspolicy_name ",
546             "at rule #$resume_rulenumber\n";
547             }
548             else {
549 0           $new_accesspolicy = $self->create_accesspolicy({
550             name => $destination_accesspolicy_name,
551             defaultAction => {
552             action => 'BLOCK',
553             logBegin => 1,
554             sendEventsToFMC => 1,
555             },
556             });
557             }
558             #say Dumper($new_accesspolicy);
559              
560 0           my $rulenumber = 1;
561 0           RULE: for my $rule ($self->list_accessrules($accesspolicy->{id},
562             { expanded => 'true' })->{items}->@*) {
563 0 0 0       if ( defined $resume_rulenumber
564             && $rulenumber < $resume_rulenumber ) {
565 0           $rulenumber++;
566 0           next RULE;
567             }
568              
569             #next RULE
570             # unless $rule->{name} eq 'outside_access_in#15-1';
571 0           say $rule->{name};
572             # copy all attributes of the existing rule to the new one
573 0           my $updated_data = clone($rule);
574             # remove attributes that are not needed/allowed in the create
575             # call
576 0           delete $updated_data->{id};
577 0           delete $updated_data->{links};
578 0           delete $updated_data->{metadata};
579 0           delete $updated_data->{commentHistoryList};
580             #my $rule_for_diff = clone($updated_data);
581              
582 0 0         if (exists $optional->{rule_name_coderef}) {
583 0           $updated_data->{name} = $optional->{rule_name_coderef}->($rulenumber, $rule);
584             }
585              
586 0           $rulenumber++;
587              
588 0           for my $networktype (qw( sourceNetworks destinationNetworks )) {
589 0           my $src_networks = $rule->{$networktype};
590 0           for my $key (keys $src_networks->%*) {
591 0 0         if ($key eq 'objects') {
592 0           $updated_data->{$networktype}->{objects} = [];
593             #say "old: " . Dumper($src_networks->{objects});
594 0           for my $network ($src_networks->{objects}->@*) {
595             #say Dumper($network);
596 0           my $name = $network->{name};
597 0           my $type = $network->{type};
598 0 0         if ( $name =~ /^DM_INLINE_/ ) {
599             # eliminate autogenerated NetworkGroups
600 0 0         if ( $type eq 'NetworkGroup' ) {
601 0           my $networkgroup = $self->get_networkgroup($network->{id});
602             my $object_count =
603             (exists $networkgroup->{objects}
604             ? scalar $networkgroup->{objects}->@*
605             : 0)
606             + (exists $networkgroup->{literals}
607             ? scalar $networkgroup->{literals}->@*
608 0 0         : 0);
    0          
609 0 0         if ( $object_count > 50 ) {
610 0           warn "\tnumber of objects (",
611             $object_count, ") would exceed ",
612             "current FMC limit of 50, ",
613             "keeping current contents\n";
614             }
615             else {
616 0           say "\tmoving contents of group $name directly into rule";
617 0           for my $objecttype (qw( objects literals )) {
618 0           for my $networkobject ($networkgroup->{$objecttype}->@*) {
619             #say Dumper($networkobject);
620 0           push $updated_data->{$networktype}->{$objecttype}->@*, $networkobject;
621             }
622             }
623             }
624             }
625             else {
626 0           warn "object type $type not supported, keeping original object!";
627 0           push $updated_data->{$networktype}->{objects}->@*, $network;
628             }
629             }
630             # keep non-autogenerated objects
631             else {
632 0           push $updated_data->{$networktype}->{objects}->@*, $network;
633             }
634             }
635             }
636             # copy all other contents
637             else {
638 0           $updated_data->{$networktype}->{$key} = $src_networks->{$key};
639             }
640             }
641             }
642              
643 0           my $ports = $rule->{destinationPorts};
644 0 0         if (exists $ports->{objects} ) {
645             $updated_data->{destinationPorts} = {
646 0           objects => []
647             };
648             #say "old: " . Dumper($ports->{objects});
649 0           for my $portobj ($ports->{objects}->@*) {
650             #say Dumper($portobj);
651 0           my $name = $portobj->{name};
652 0           my $type = $portobj->{type};
653              
654 0 0         if ( $name =~ /^DM_INLINE_/ ) {
655             # eliminate autogenerated PortObjectGroups
656 0 0         if ( $type eq 'ProtocolPortObject' ) {
    0          
    0          
657             push
658             $updated_data->{destinationPorts}->{objects}->@*,
659             $self->cleanup_protocolport({
660 0           %$portobj{qw( id type )} });
661             }
662             elsif ( $type eq 'ICMPV4Object' ) {
663             push
664             $updated_data->{destinationPorts}->{objects}->@*,
665             $self->cleanup_icmpv4object({
666 0           %$portobj{qw( id type )} });
667             }
668             elsif ( $type eq 'PortObjectGroup' ) {
669 0           say "\tmoving contents of group $name directly into rule";
670             #say Dumper($rule);
671 0           my $portobjectgroup = $self->get_portobjectgroup($portobj->{id});
672 0           for my $portobject ($portobjectgroup->{objects}->@*) {
673             #say Dumper($portobject);
674             my $object = $portobject->{type} eq
675             'ProtocolPortObject'
676             ? $self->cleanup_protocolport(
677             {%$portobject{qw( id type )}}
678             )
679 0 0         : {%$portobject{qw( id type )}};
680             push $updated_data->{destinationPorts}->{objects}->@*,
681 0           $object;
682             }
683             }
684             else {
685 0           warn "unhandled object type $type, keeping unmodified\n";
686 0           push $updated_data->{destinationPorts}->{objects}->@*, $portobj;
687             }
688              
689             #my $protocolportobject = $self->get_protocolportobject($portobj->{id});
690             #say Dumper($protocolportobject);
691             }
692             # keep non-autogenerated objects
693             else {
694 0           push $updated_data->{destinationPorts}->{objects}->@*, $portobj;
695             }
696             }
697             }
698             #say "new: " . Dumper($updated_data);
699             # always replace existing destinationPorts because one of
700             # them might have been replaced with an existing one
701             # FIXME: check if literals get lost
702             #$self->update_accessrule($accesspolicy->{id}, $rule, $updated_data)
703             # if $updated_data->%*;
704             #say Dumper($updated_data);
705             #use Test::Differences;
706             #eq_or_diff($updated_data, $rule_for_diff, 'rule');
707 0           $self->create_accessrule($new_accesspolicy->{id}, $updated_data);
708             #last RULE;
709             }
710             }
711             }
712              
713             1;
714              
715             __END__
716              
717             =pod
718              
719             =encoding UTF-8
720              
721             =head1 NAME
722              
723             Net::Cisco::FMC::v1 - Cisco Firepower Management Center (FMC) API version 1 client library
724              
725             =head1 VERSION
726              
727             version 0.009002
728              
729             =head1 SYNOPSIS
730              
731             use strict;
732             use warnings;
733             use Net::Cisco::FMC::v1;
734             use Data::Dumper::Concise;
735              
736             my $fmc = Net::Cisco::FMC::v1->new(
737             server => 'https://fmcrestapisandbox.cisco.com',
738             user => 'admin',
739             passwd => '$password',
740             clientattrs => { timeout => 30 },
741             );
742              
743             # login to populate domains
744             $fmc->login;
745              
746             # list all domain uuids and names
747             print Dumper($fmc->domains);
748             # switch domain
749             $fmc->domain_uuid("e276abec-e0f2-11e3-8169-6d9ed49b625f");
750              
751             =head1 DESCRIPTION
752              
753             This module is a client library for the Cisco Firepower Management
754             Center (FMC) REST API version 1.
755             Currently it is developed and tested against FMC version 7.2.0.1.
756              
757             =head1 ATTRIBUTES
758              
759             =head2 domains
760              
761             Returns a list of hashrefs containing name and uuid of all domains which gets
762             populated by L</login>.
763              
764             =head2 domain_uuid
765              
766             The UUID of the domain which is used by all methods.
767              
768             =head1 METHODS
769              
770             =head2 login
771              
772             Logs into the FMC by fetching an authentication token via http basic
773             authentication.
774              
775             =head2 relogin
776              
777             Refreshes the session by loging in again (not using the refresh token) and
778             restores the currently set domain_uuid.
779              
780             =head2 logout
781              
782             Logs out of the FMC.
783              
784             =head2 create_accessrule
785              
786             Takes an access policy id, a hashref of the rule which should be created and
787             optional query parameters.
788              
789             =head2 list_accessrules
790              
791             Takes an access policy id and query parameters and returns a hashref with a
792             single key 'items' that has a list of access rules similar to the FMC API.
793              
794             =head2 get_accessrule
795              
796             Takes an access policy id, rule id and query parameters and returns the access
797             rule.
798              
799             =head2 update_accessrule
800              
801             Takes an access policy id, rule object, a hashref of the rule and an optional
802             hashref of query parameters and returns a hashref of the updated access rule.
803              
804             =head2 delete_accessrule
805              
806             Takes an access policy id and a rule object id.
807              
808             Returns true on success.
809              
810             =head2 list_deployabledevices
811              
812             Takes optional query parameters and returns a hashref with a
813             single key 'items' that has a list of deployable devices similar to the FMC
814             API.
815              
816             =head2 create_deploymentrequest
817              
818             Takes a hashref of deployment parameters.
819              
820             Returns the created task in the ->{metadata}->{task} hashref.
821              
822             =head2 get_task
823              
824             Takes a task id and returns its status.
825              
826             =head2 wait_for_task
827              
828             Takes a task id and an optional callback and checks its status every second
829             until it isn't in-progress any more.
830             The in-progress status is different for each task type, currently only
831             'DEVICE_DEPLOYMENT' is supported.
832             The callback coderef which is called for every check with the task as argument.
833              
834             Returns the task.
835              
836             =head2 cleanup_protocolport
837              
838             Takes a ProtocolPortObject and renames it to protocol_port, e.g. tcp_443.
839             If it has no port 'any' is used instead of the port number no avoid
840             'predefined name' errors.
841             Returns the ProtocolPortObject with the updated attributes.
842              
843             =head2 cleanup_icmpv4object
844              
845             Takes a ICMPv4Object and renames it to protocol_type[_code], e.g. icmp_8_0.
846             If it has no code only protocol and type is used.
847              
848             =head2 cleanup_hosts
849              
850             =over
851              
852             =item removes '_Mask32' from the name
853              
854             =item removes the description if it is 'Created during ASA Migration'
855              
856             =back
857              
858             =head2 create_cleaned_accesspolicy
859              
860             Takes an access policy name and a hashref of optional arguments.
861              
862             =head3 Optional arguments
863              
864             =over
865              
866             =item target_access_policy_name
867              
868             Defaults to access policy name with the postfix '-cleaned'.
869              
870             =item rule_name_coderef
871              
872             Gets passed the rule number and rule object and must return the new rule name.
873              
874             =back
875              
876             Creates a new access policy with the target name containing all rules of the
877             input access policy but cleaned by the following rules:
878              
879             =over
880              
881             =item the commentHistoryList is omitted
882              
883             =item replace autogenerated DM_INLINE_ NetworkGroups by their content
884              
885             Only if they don't contain more than 50 items because of the current limit in
886             FMC.
887              
888             =item replace autogenerated DM_INLINE_ PortObjectGroups by their content
889              
890             =item optional: the rule name is generated
891              
892             By passing a coderef named 'rule_name_coderef' in the optional arguments
893             hashref.
894              
895             =back
896              
897             The new access policy is created with a defaultAction of:
898              
899             action => 'BLOCK'
900             logBegin => true
901             sendEventsToFMC => true
902              
903             This is mainly for access policies migrated by the Cisco Firepower Migration
904             Tool from a Cisco ASA.
905              
906             Supports resuming.
907              
908             =head1 KNOWN BUGS
909              
910             Older FMC versions have bugs like:
911              
912             =over
913              
914             =item truncated JSON responses
915              
916             No workaround on client side possible, only a FMC update helps.
917              
918             =item no response to the 11th call (version 6.2.2.1)
919              
920             No workaround on client side because newer FMC versions (at least 6.2.3.6)
921             throttle the login call too.
922              
923             =item accessrule is created but error 'You do not have the required
924             authorization to do this operation' is thrown (version 6.2.2)
925              
926             No workaround on client side possible, only a FMC update helps.
927              
928             =back
929              
930             =head1 AUTHOR
931              
932             Alexander Hartmaier <abraxxa@cpan.org>
933              
934             =head1 COPYRIGHT AND LICENSE
935              
936             This software is copyright (c) 2018 - 2023 by Alexander Hartmaier.
937              
938             This is free software; you can redistribute it and/or modify it under
939             the same terms as the Perl 5 programming language system itself.
940              
941             =cut