Skip to content

Commit 43d18f5

Browse files
committed
proxmox_firewall: Add force condition
- state=present: + check if fw rules already exists and if needed update them instead of creating + check if group exists and if so don't do anything - state=update: + check if fw rules don't existsand if needed create them instead of updating - make rules.pos as required this is to handle above conditions - add method to get security groups and list them with firewall rules when state is not provided - add proxmox_firewall in meta/runtime.yml
1 parent 47d52ca commit 43d18f5

File tree

2 files changed

+101
-17
lines changed

2 files changed

+101
-17
lines changed

meta/runtime.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,4 @@ action_groups:
3535
- proxmox_user
3636
- proxmox_user_info
3737
- proxmox_vm_info
38+
- proxmox_firewall

plugins/modules/proxmox_firewall.py

Lines changed: 100 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@
3232
- present
3333
- update
3434
- 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
3541
level:
3642
description:
3743
- Level at which the firewall rule applies.
@@ -157,6 +163,7 @@
157163
description:
158164
- Position of the rule in the list.
159165
type: int
166+
required: true
160167
proto:
161168
description:
162169
- IP protocol. You can use protocol names ('tcp'/'udp') or simple numbers, as defined in '/etc/protocols'.
@@ -276,6 +283,14 @@
276283
sample:
277284
test
278285
286+
groups:
287+
description: list of firewall security groups
288+
returned: on success
289+
type: list
290+
elements: str
291+
sample:
292+
[ "test" ]
293+
279294
firewall_rules:
280295
description: List of firewall rules.
281296
returned: on success
@@ -391,6 +406,7 @@
391406
def get_proxmox_args():
392407
return dict(
393408
state=dict(type="str", choices=["present", "absent", "update"], required=False),
409+
force=dict(type="bool", default=False),
394410
level=dict(type="str", choices=["cluster", "node", "vm", "vnet", "group"], default="cluster", required=False),
395411
node=dict(type="str", required=False),
396412
vmid=dict(type="int", required=False),
@@ -417,7 +433,7 @@ def get_proxmox_args():
417433
choices=["emerg", "alert", "crit", "err", "warning", "notice", "info", "debug", "nolog"],
418434
required=False),
419435
macro=dict(type="str", required=False),
420-
pos=dict(type="int", required=False),
436+
pos=dict(type="int", required=True),
421437
proto=dict(type="str", required=False),
422438
source=dict(type="str", required=False),
423439
sport=dict(type="str", required=False)
@@ -449,21 +465,29 @@ def __init__(self, module):
449465

450466
def validate_params(self):
451467
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+
)
453475
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+
)
455483
else:
456484
return True
457485

458486
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()
465488

466489
state = self.params.get("state")
490+
force = self.params.get("force")
467491
level = self.params.get("level")
468492
rules = self.params.get("rules")
469493

@@ -496,24 +520,32 @@ def run(self):
496520
if self.params.get('group_conf'):
497521
self.create_group(group=self.params.get('group'), comment=self.params.get('comment'))
498522
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)
500524
elif state == "update":
501525
if self.params.get('group_conf'):
502526
self.create_group(group=self.params.get('group'), comment=self.params.get('comment'))
503527
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)
505529
elif state == "absent":
506530
if self.params.get('pos'):
507531
self.delete_fw_rule(rules_obj=rules_obj, pos=self.params.get('pos'))
508532
if self.params.get('group_conf'):
509533
self.delete_group(group_name=self.params.get('group'))
510534
else:
511535
rules = self.get_fw_rules(rules_obj, pos=self.params.get('pos'))
536+
groups = self.get_groups()
512537
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'
514542
)
515543

516544
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+
)
517549
try:
518550
self.proxmox_api.cluster().firewall().groups.post(group=group, comment=comment)
519551
self.module.exit_json(
@@ -525,6 +557,10 @@ def create_group(self, group, comment=None):
525557
)
526558

527559
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+
)
528564
try:
529565
group = getattr(self.proxmox_api.cluster().firewall().groups(), group_name)
530566
group.delete()
@@ -546,8 +582,23 @@ def get_fw_rules(self, rules_obj, pos=None):
546582
msg=f'Failed to retrieve firewall rules: {e}'
547583
)
548584

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+
549593
def delete_fw_rule(self, rules_obj, pos):
550594
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+
)
551602
rule_obj = getattr(rules_obj(), str(pos))
552603
digest = rule_obj.get().get('digest')
553604
rule_obj.delete(pos=pos, digest=digest)
@@ -560,8 +611,21 @@ def delete_fw_rule(self, rules_obj, pos):
560611
msg=f'Failed to delete firewall rule at pos {pos}: {e}'
561612
)
562613

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:
565629
rule['icmp-type'] = rule.get('icmp_type')
566630
rule['enable'] = ansible_to_proxmox_bool(rule.get('enable'))
567631
del rule['icmp_type']
@@ -574,12 +638,29 @@ def update_fw_rules(self, rules_obj, rules):
574638
self.module.fail_json(
575639
msg=f'Failed to update firewall rule at pos {rule["pos"]}: {e}'
576640
)
641+
642+
if len(rules_to_create) > 0:
643+
self.create_fw_rules(rules_obj=rules_obj, rules=rules_to_create, force=False)
577644
self.module.exit_json(
578-
changed=True, msg='successfully created firewall rules'
645+
changed=True, msg='successfully updated firewall rules'
579646
)
580647

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:
583664
rule['icmp-type'] = rule.get('icmp_type')
584665
rule['enable'] = ansible_to_proxmox_bool(rule.get('enable'))
585666
del rule['icmp_type']
@@ -591,6 +672,8 @@ def create_fw_rules(self, rules_obj, rules):
591672
self.module.fail_json(
592673
msg=f'Failed to create firewall rule {rule}: {e}'
593674
)
675+
if len(rules_to_update) > 0:
676+
self.update_fw_rules(rules_obj=rules_obj, rules=rules_to_update, force=False)
594677
self.module.exit_json(
595678
changed=True, msg='successfully created firewall rules'
596679
)

0 commit comments

Comments
 (0)