32
32
- present
33
33
- update
34
34
- absent
35
+ force:
36
+ description:
37
+ - If state is present and if 1 or more rule already exists at given pos force will update them
38
+ - If state is update and if 1 or more rule doesn't exist force will create
39
+ type: bool
40
+ default: false
35
41
level:
36
42
description:
37
43
- Level at which the firewall rule applies.
157
163
description:
158
164
- Position of the rule in the list.
159
165
type: int
166
+ required: true
160
167
proto:
161
168
description:
162
169
- IP protocol. You can use protocol names ('tcp'/'udp') or simple numbers, as defined in '/etc/protocols'.
276
283
sample:
277
284
test
278
285
286
+ groups:
287
+ description: list of firewall security groups
288
+ returned: on success
289
+ type: list
290
+ elements: str
291
+ sample:
292
+ [ "test" ]
293
+
279
294
firewall_rules:
280
295
description: List of firewall rules.
281
296
returned: on success
391
406
def get_proxmox_args ():
392
407
return dict (
393
408
state = dict (type = "str" , choices = ["present" , "absent" , "update" ], required = False ),
409
+ force = dict (type = "bool" , default = False ),
394
410
level = dict (type = "str" , choices = ["cluster" , "node" , "vm" , "vnet" , "group" ], default = "cluster" , required = False ),
395
411
node = dict (type = "str" , required = False ),
396
412
vmid = dict (type = "int" , required = False ),
@@ -417,7 +433,7 @@ def get_proxmox_args():
417
433
choices = ["emerg" , "alert" , "crit" , "err" , "warning" , "notice" , "info" , "debug" , "nolog" ],
418
434
required = False ),
419
435
macro = dict (type = "str" , required = False ),
420
- pos = dict (type = "int" , required = False ),
436
+ pos = dict (type = "int" , required = True ),
421
437
proto = dict (type = "str" , required = False ),
422
438
source = dict (type = "str" , required = False ),
423
439
sport = dict (type = "str" , required = False )
@@ -449,21 +465,29 @@ def __init__(self, module):
449
465
450
466
def validate_params (self ):
451
467
if self .params .get ('state' ) in ['present' , 'update' ]:
452
- return self .params .get ('group_conf' ) or self .params .get ('rules' )
468
+ if ((self .params .get ('group_conf' ) and self .params .get ('rules' ) is None ) or
469
+ (not self .params .get ('group_conf' ) and self .params .get ('rules' ) is not None )):
470
+ return True
471
+ else :
472
+ self .module .fail_json (
473
+ msg = "When state is present either group_conf should be true or rules must be present but not both"
474
+ )
453
475
elif self .params .get ('state' ) == 'absent' :
454
- return self .params .get ('group_conf' ) or self .params .get ('pos' )
476
+ if ((self .params .get ('group_conf' ) and self .params .get ('pos' ) is None ) or
477
+ (not self .params .get ('group_conf' ) and self .params .get ('pos' ) is not None )):
478
+ return True
479
+ else :
480
+ self .module .fail_json (
481
+ msg = "When State is absent either group_conf should be true or pos must be present but not both"
482
+ )
455
483
else :
456
484
return True
457
485
458
486
def run (self ):
459
- if not self .validate_params ():
460
- self .module .fail_json (
461
- msg = 'parameter validation failed. '
462
- 'If state is present/update we need either group_conf to be True or rules to be present. '
463
- 'If state is absent we need group_conf to be True or pos to be present. '
464
- )
487
+ self .validate_params ()
465
488
466
489
state = self .params .get ("state" )
490
+ force = self .params .get ("force" )
467
491
level = self .params .get ("level" )
468
492
rules = self .params .get ("rules" )
469
493
@@ -496,24 +520,32 @@ def run(self):
496
520
if self .params .get ('group_conf' ):
497
521
self .create_group (group = self .params .get ('group' ), comment = self .params .get ('comment' ))
498
522
if rules is not None :
499
- self .create_fw_rules (rules_obj = rules_obj , rules = rules )
523
+ self .create_fw_rules (rules_obj = rules_obj , rules = rules , force = force )
500
524
elif state == "update" :
501
525
if self .params .get ('group_conf' ):
502
526
self .create_group (group = self .params .get ('group' ), comment = self .params .get ('comment' ))
503
527
if rules is not None :
504
- self .update_fw_rules (rules_obj = rules_obj , rules = rules )
528
+ self .update_fw_rules (rules_obj = rules_obj , rules = rules , force = force )
505
529
elif state == "absent" :
506
530
if self .params .get ('pos' ):
507
531
self .delete_fw_rule (rules_obj = rules_obj , pos = self .params .get ('pos' ))
508
532
if self .params .get ('group_conf' ):
509
533
self .delete_group (group_name = self .params .get ('group' ))
510
534
else :
511
535
rules = self .get_fw_rules (rules_obj , pos = self .params .get ('pos' ))
536
+ groups = self .get_groups ()
512
537
self .module .exit_json (
513
- changed = False , firewall_rules = rules , msg = 'successfully retrieved firewall rules'
538
+ changed = False ,
539
+ firewall_rules = rules ,
540
+ groups = groups ,
541
+ msg = 'successfully retrieved firewall rules and groups'
514
542
)
515
543
516
544
def create_group (self , group , comment = None ):
545
+ if group in self .get_groups ():
546
+ self .module .exit_json (
547
+ changed = False , group = group , msg = f"security group { group } already exists"
548
+ )
517
549
try :
518
550
self .proxmox_api .cluster ().firewall ().groups .post (group = group , comment = comment )
519
551
self .module .exit_json (
@@ -525,6 +557,10 @@ def create_group(self, group, comment=None):
525
557
)
526
558
527
559
def delete_group (self , group_name ):
560
+ if group_name not in self .get_groups ():
561
+ self .module .exit_json (
562
+ changed = False , group = group_name , msg = f"security group { group_name } already doesn't exists"
563
+ )
528
564
try :
529
565
group = getattr (self .proxmox_api .cluster ().firewall ().groups (), group_name )
530
566
group .delete ()
@@ -546,8 +582,23 @@ def get_fw_rules(self, rules_obj, pos=None):
546
582
msg = f'Failed to retrieve firewall rules: { e } '
547
583
)
548
584
585
+ def get_groups (self ):
586
+ try :
587
+ return [x ['group' ] for x in self .proxmox_api .cluster ().firewall ().groups ().get ()]
588
+ except Exception as e :
589
+ self .module .fail_json (
590
+ msg = f'Failed to retrieve firewall security groups: { e } '
591
+ )
592
+
549
593
def delete_fw_rule (self , rules_obj , pos ):
550
594
try :
595
+ for item in self .get_fw_rules (rules_obj ):
596
+ if item .get ('pos' ) == pos :
597
+ break
598
+ else :
599
+ self .module .exit_json (
600
+ changed = False , msg = "Firewall rule already doesn't exist"
601
+ )
551
602
rule_obj = getattr (rules_obj (), str (pos ))
552
603
digest = rule_obj .get ().get ('digest' )
553
604
rule_obj .delete (pos = pos , digest = digest )
@@ -560,8 +611,21 @@ def delete_fw_rule(self, rules_obj, pos):
560
611
msg = f'Failed to delete firewall rule at pos { pos } : { e } '
561
612
)
562
613
563
- def update_fw_rules (self , rules_obj , rules ):
564
- for rule in rules :
614
+ def update_fw_rules (self , rules_obj , rules , force ):
615
+ existing_rules = self .get_fw_rules (rules_obj )
616
+ rules_to_create = []
617
+ if len (existing_rules ) > 0 :
618
+ existing_pos = [x ['pos' ] for x in existing_rules ]
619
+ for rule in rules :
620
+ if rule .get ('pos' ) not in existing_pos :
621
+ if force :
622
+ rules_to_create .append (rule )
623
+ else :
624
+ self .module .fail_json (
625
+ msg = f"Rule doesn't exists at pos - { rule .get ('pos' )} and force is false."
626
+ )
627
+ rules_to_update = [rule for rule in rules if rule not in rules_to_create ]
628
+ for rule in rules_to_update :
565
629
rule ['icmp-type' ] = rule .get ('icmp_type' )
566
630
rule ['enable' ] = ansible_to_proxmox_bool (rule .get ('enable' ))
567
631
del rule ['icmp_type' ]
@@ -574,12 +638,29 @@ def update_fw_rules(self, rules_obj, rules):
574
638
self .module .fail_json (
575
639
msg = f'Failed to update firewall rule at pos { rule ["pos" ]} : { e } '
576
640
)
641
+
642
+ if len (rules_to_create ) > 0 :
643
+ self .create_fw_rules (rules_obj = rules_obj , rules = rules_to_create , force = False )
577
644
self .module .exit_json (
578
- changed = True , msg = 'successfully created firewall rules'
645
+ changed = True , msg = 'successfully updated firewall rules'
579
646
)
580
647
581
- def create_fw_rules (self , rules_obj , rules ):
582
- for rule in rules :
648
+ def create_fw_rules (self , rules_obj , rules , force ):
649
+ existing_rules = self .get_fw_rules (rules_obj )
650
+ rules_to_update = []
651
+ if len (existing_rules ) > 0 :
652
+ existing_pos = [x ['pos' ] for x in existing_rules ]
653
+ for rule in rules :
654
+ if rule .get ('pos' ) in existing_pos :
655
+ if force :
656
+ rules_to_update .append (rule )
657
+ else :
658
+ self .module .fail_json (
659
+ msg = f"Rule already exists at pos - { rule .get ('pos' )} and force is false."
660
+ )
661
+ rules_to_create = [rule for rule in rules if rule not in rules_to_update ]
662
+
663
+ for rule in rules_to_create :
583
664
rule ['icmp-type' ] = rule .get ('icmp_type' )
584
665
rule ['enable' ] = ansible_to_proxmox_bool (rule .get ('enable' ))
585
666
del rule ['icmp_type' ]
@@ -591,6 +672,8 @@ def create_fw_rules(self, rules_obj, rules):
591
672
self .module .fail_json (
592
673
msg = f'Failed to create firewall rule { rule } : { e } '
593
674
)
675
+ if len (rules_to_update ) > 0 :
676
+ self .update_fw_rules (rules_obj = rules_obj , rules = rules_to_update , force = False )
594
677
self .module .exit_json (
595
678
changed = True , msg = 'successfully created firewall rules'
596
679
)
0 commit comments