cloudstack: cs_firewall: add egress support
Added functionality to set rules for egress using this module at these are very similar. The only real difference is that egress firewall API uses the networkid. That is why the new arguments `type` for choosing `egress` or `ingress` and `network` was added. For `type=ingress`, which is the default, `ip_address` is required and for `type=egress` the argument `network` is required.
This commit is contained in:
parent
8147103da9
commit
2da7792ff6
1 changed files with 141 additions and 21 deletions
|
@ -29,20 +29,35 @@ author: '"René Moser (@resmo)" <mail@renemoser.net>'
|
||||||
options:
|
options:
|
||||||
ip_address:
|
ip_address:
|
||||||
description:
|
description:
|
||||||
- Public IP address the rule is assigned to.
|
- Public IP address the ingress rule is assigned to.
|
||||||
required: true
|
- Required if C(type=ingress).
|
||||||
|
required: false
|
||||||
|
default: null
|
||||||
|
network:
|
||||||
|
description:
|
||||||
|
- Network the egress rule is related to.
|
||||||
|
- Required if C(type=egress).
|
||||||
|
required: false
|
||||||
|
default: null
|
||||||
state:
|
state:
|
||||||
description:
|
description:
|
||||||
- State of the firewall rule.
|
- State of the firewall rule.
|
||||||
required: false
|
required: false
|
||||||
default: 'present'
|
default: 'present'
|
||||||
choices: [ 'present', 'absent' ]
|
choices: [ 'present', 'absent' ]
|
||||||
|
type:
|
||||||
|
description:
|
||||||
|
- Type of the firewall rule.
|
||||||
|
required: false
|
||||||
|
default: 'ingress'
|
||||||
|
choices: [ 'ingress', 'egress' ]
|
||||||
protocol:
|
protocol:
|
||||||
description:
|
description:
|
||||||
- Protocol of the firewall rule.
|
- Protocol of the firewall rule.
|
||||||
|
- C(all) is only available if C(type=egress)
|
||||||
required: false
|
required: false
|
||||||
default: 'tcp'
|
default: 'tcp'
|
||||||
choices: [ 'tcp', 'udp', 'icmp' ]
|
choices: [ 'tcp', 'udp', 'icmp', 'all' ]
|
||||||
cidr:
|
cidr:
|
||||||
description:
|
description:
|
||||||
- CIDR (full notation) to be used for firewall rule.
|
- CIDR (full notation) to be used for firewall rule.
|
||||||
|
@ -84,6 +99,11 @@ options:
|
||||||
- Name of the project the firewall rule is related to.
|
- Name of the project the firewall rule is related to.
|
||||||
required: false
|
required: false
|
||||||
default: null
|
default: null
|
||||||
|
poll_async:
|
||||||
|
description:
|
||||||
|
- Poll async jobs until job has finished.
|
||||||
|
required: false
|
||||||
|
default: true
|
||||||
extends_documentation_fragment: cloudstack
|
extends_documentation_fragment: cloudstack
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
@ -115,15 +135,37 @@ EXAMPLES = '''
|
||||||
end_port: 8888
|
end_port: 8888
|
||||||
cidr: 17.0.0.0/8
|
cidr: 17.0.0.0/8
|
||||||
state: absent
|
state: absent
|
||||||
|
|
||||||
|
|
||||||
|
# Allow all outbound traffic
|
||||||
|
- local_action:
|
||||||
|
module: cs_firewall
|
||||||
|
network: my_network
|
||||||
|
type: egress
|
||||||
|
protocol: all
|
||||||
|
|
||||||
|
|
||||||
|
# Allow only HTTP outbound traffic for an IP
|
||||||
|
- local_action:
|
||||||
|
module: cs_firewall
|
||||||
|
network: my_network
|
||||||
|
type: egress
|
||||||
|
port: 80
|
||||||
|
cidr: 10.101.1.20
|
||||||
'''
|
'''
|
||||||
|
|
||||||
RETURN = '''
|
RETURN = '''
|
||||||
---
|
---
|
||||||
ip_address:
|
ip_address:
|
||||||
description: IP address of the rule.
|
description: IP address of the rule if C(type=ingress)
|
||||||
returned: success
|
returned: success
|
||||||
type: string
|
type: string
|
||||||
sample: 10.100.212.10
|
sample: 10.100.212.10
|
||||||
|
type:
|
||||||
|
description: Type of the rule.
|
||||||
|
returned: success
|
||||||
|
type: string
|
||||||
|
sample: ingress
|
||||||
cidr:
|
cidr:
|
||||||
description: CIDR of the rule.
|
description: CIDR of the rule.
|
||||||
returned: success
|
returned: success
|
||||||
|
@ -154,6 +196,11 @@ icmp_type:
|
||||||
returned: success
|
returned: success
|
||||||
type: int
|
type: int
|
||||||
sample: 1
|
sample: 1
|
||||||
|
network:
|
||||||
|
description: Name of the network if C(type=egress)
|
||||||
|
returned: success
|
||||||
|
type: string
|
||||||
|
sample: my_network
|
||||||
'''
|
'''
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -181,32 +228,46 @@ class AnsibleCloudStackFirewall(AnsibleCloudStack):
|
||||||
|
|
||||||
def get_firewall_rule(self):
|
def get_firewall_rule(self):
|
||||||
if not self.firewall_rule:
|
if not self.firewall_rule:
|
||||||
cidr = self.module.params.get('cidr')
|
cidr = self.module.params.get('cidr')
|
||||||
protocol = self.module.params.get('protocol')
|
protocol = self.module.params.get('protocol')
|
||||||
start_port = self.module.params.get('start_port')
|
start_port = self.module.params.get('start_port')
|
||||||
end_port = self.get_end_port()
|
end_port = self.get_end_port()
|
||||||
icmp_code = self.module.params.get('icmp_code')
|
icmp_code = self.module.params.get('icmp_code')
|
||||||
icmp_type = self.module.params.get('icmp_type')
|
icmp_type = self.module.params.get('icmp_type')
|
||||||
|
fw_type = self.module.params.get('type')
|
||||||
|
|
||||||
if protocol in ['tcp', 'udp'] and not (start_port and end_port):
|
if protocol in ['tcp', 'udp'] and not (start_port and end_port):
|
||||||
self.module.fail_json(msg="no start_port or end_port set for protocol '%s'" % protocol)
|
self.module.fail_json(msg="missing required argument for protocol '%s': start_port or end_port" % protocol)
|
||||||
|
|
||||||
if protocol == 'icmp' and not icmp_type:
|
if protocol == 'icmp' and not icmp_type:
|
||||||
self.module.fail_json(msg="no icmp_type set")
|
self.module.fail_json(msg="missing required argument for protocol 'icmp': icmp_type")
|
||||||
|
|
||||||
|
if protocol == 'all' and fw_type != 'egress':
|
||||||
|
self.module.fail_json(msg="protocol 'all' could only be used for type 'egress'" )
|
||||||
|
|
||||||
args = {}
|
args = {}
|
||||||
args['ipaddressid'] = self.get_ip_address('id')
|
|
||||||
args['account'] = self.get_account('name')
|
args['account'] = self.get_account('name')
|
||||||
args['domainid'] = self.get_domain('id')
|
args['domainid'] = self.get_domain('id')
|
||||||
args['projectid'] = self.get_project('id')
|
args['projectid'] = self.get_project('id')
|
||||||
|
|
||||||
firewall_rules = self.cs.listFirewallRules(**args)
|
if fw_type == 'egress':
|
||||||
|
args['networkid'] = self.get_network(key='id')
|
||||||
|
if not args['networkid']:
|
||||||
|
self.module.fail_json(msg="missing required argument for type egress: network")
|
||||||
|
firewall_rules = self.cs.listEgressFirewallRules(**args)
|
||||||
|
else:
|
||||||
|
args['ipaddressid'] = self.get_ip_address('id')
|
||||||
|
if not args['ipaddressid']:
|
||||||
|
self.module.fail_json(msg="missing required argument for type ingress: ip_address")
|
||||||
|
firewall_rules = self.cs.listFirewallRules(**args)
|
||||||
|
|
||||||
if firewall_rules and 'firewallrule' in firewall_rules:
|
if firewall_rules and 'firewallrule' in firewall_rules:
|
||||||
for rule in firewall_rules['firewallrule']:
|
for rule in firewall_rules['firewallrule']:
|
||||||
type_match = self._type_cidr_match(rule, cidr)
|
type_match = self._type_cidr_match(rule, cidr)
|
||||||
|
|
||||||
protocol_match = self._tcp_udp_match(rule, protocol, start_port, end_port) \
|
protocol_match = self._tcp_udp_match(rule, protocol, start_port, end_port) \
|
||||||
or self._icmp_match(rule, protocol, icmp_code, icmp_type)
|
or self._icmp_match(rule, protocol, icmp_code, icmp_type) \
|
||||||
|
or self._egress_all_match(rule, protocol, fw_type)
|
||||||
|
|
||||||
if type_match and protocol_match:
|
if type_match and protocol_match:
|
||||||
self.firewall_rule = rule
|
self.firewall_rule = rule
|
||||||
|
@ -221,6 +282,12 @@ class AnsibleCloudStackFirewall(AnsibleCloudStack):
|
||||||
and end_port == int(rule['endport'])
|
and end_port == int(rule['endport'])
|
||||||
|
|
||||||
|
|
||||||
|
def _egress_all_match(self, rule, protocol, fw_type):
|
||||||
|
return protocol in ['all'] \
|
||||||
|
and protocol == rule['protocol'] \
|
||||||
|
and fw_type == 'egress'
|
||||||
|
|
||||||
|
|
||||||
def _icmp_match(self, rule, protocol, icmp_code, icmp_type):
|
def _icmp_match(self, rule, protocol, icmp_code, icmp_type):
|
||||||
return protocol == 'icmp' \
|
return protocol == 'icmp' \
|
||||||
and protocol == rule['protocol'] \
|
and protocol == rule['protocol'] \
|
||||||
|
@ -232,6 +299,30 @@ class AnsibleCloudStackFirewall(AnsibleCloudStack):
|
||||||
return cidr == rule['cidrlist']
|
return cidr == rule['cidrlist']
|
||||||
|
|
||||||
|
|
||||||
|
def get_network(self, key=None, network=None):
|
||||||
|
if not network:
|
||||||
|
network = self.module.params.get('network')
|
||||||
|
|
||||||
|
if not network:
|
||||||
|
return None
|
||||||
|
|
||||||
|
args = {}
|
||||||
|
args['account'] = self.get_account('name')
|
||||||
|
args['domainid'] = self.get_domain('id')
|
||||||
|
args['projectid'] = self.get_project('id')
|
||||||
|
args['zoneid'] = self.get_zone('id')
|
||||||
|
|
||||||
|
networks = self.cs.listNetworks(**args)
|
||||||
|
if not networks:
|
||||||
|
self.module.fail_json(msg="No networks available")
|
||||||
|
|
||||||
|
for n in networks['network']:
|
||||||
|
if network in [ n['displaytext'], n['name'], n['id'] ]:
|
||||||
|
return self._get_by_key(key, n)
|
||||||
|
break
|
||||||
|
self.module.fail_json(msg="Network '%s' not found" % network)
|
||||||
|
|
||||||
|
|
||||||
def create_firewall_rule(self):
|
def create_firewall_rule(self):
|
||||||
firewall_rule = self.get_firewall_rule()
|
firewall_rule = self.get_firewall_rule()
|
||||||
if not firewall_rule:
|
if not firewall_rule:
|
||||||
|
@ -244,11 +335,22 @@ class AnsibleCloudStackFirewall(AnsibleCloudStack):
|
||||||
args['endport'] = self.get_end_port()
|
args['endport'] = self.get_end_port()
|
||||||
args['icmptype'] = self.module.params.get('icmp_type')
|
args['icmptype'] = self.module.params.get('icmp_type')
|
||||||
args['icmpcode'] = self.module.params.get('icmp_code')
|
args['icmpcode'] = self.module.params.get('icmp_code')
|
||||||
args['ipaddressid'] = self.get_ip_address('id')
|
|
||||||
|
|
||||||
|
fw_type = self.module.params.get('type')
|
||||||
if not self.module.check_mode:
|
if not self.module.check_mode:
|
||||||
firewall_rule = self.cs.createFirewallRule(**args)
|
if fw_type == 'egress':
|
||||||
|
args['networkid'] = self.get_network(key='id')
|
||||||
|
res = self.cs.createEgressFirewallRule(**args)
|
||||||
|
else:
|
||||||
|
args['ipaddressid'] = self.get_ip_address('id')
|
||||||
|
res = self.cs.createFirewallRule(**args)
|
||||||
|
|
||||||
|
if 'errortext' in res:
|
||||||
|
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||||
|
|
||||||
|
poll_async = self.module.params.get('poll_async')
|
||||||
|
if poll_async:
|
||||||
|
firewall_rule = self._poll_job(res, 'firewallrule')
|
||||||
return firewall_rule
|
return firewall_rule
|
||||||
|
|
||||||
|
|
||||||
|
@ -256,17 +358,29 @@ class AnsibleCloudStackFirewall(AnsibleCloudStack):
|
||||||
firewall_rule = self.get_firewall_rule()
|
firewall_rule = self.get_firewall_rule()
|
||||||
if firewall_rule:
|
if firewall_rule:
|
||||||
self.result['changed'] = True
|
self.result['changed'] = True
|
||||||
args = {}
|
|
||||||
|
args = {}
|
||||||
args['id'] = firewall_rule['id']
|
args['id'] = firewall_rule['id']
|
||||||
|
|
||||||
|
fw_type = self.module.params.get('type')
|
||||||
if not self.module.check_mode:
|
if not self.module.check_mode:
|
||||||
res = self.cs.deleteFirewallRule(**args)
|
if fw_type == 'egress':
|
||||||
|
res = self.cs.deleteEgressFirewallRule(**args)
|
||||||
|
else:
|
||||||
|
res = self.cs.deleteFirewallRule(**args)
|
||||||
|
|
||||||
|
if 'errortext' in res:
|
||||||
|
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||||
|
|
||||||
|
poll_async = self.module.params.get('poll_async')
|
||||||
|
if poll_async:
|
||||||
|
res = self._poll_job(res, 'firewallrule')
|
||||||
return firewall_rule
|
return firewall_rule
|
||||||
|
|
||||||
|
|
||||||
def get_result(self, firewall_rule):
|
def get_result(self, firewall_rule):
|
||||||
if firewall_rule:
|
if firewall_rule:
|
||||||
|
self.result['type'] = self.module.params.get('type')
|
||||||
if 'cidrlist' in firewall_rule:
|
if 'cidrlist' in firewall_rule:
|
||||||
self.result['cidr'] = firewall_rule['cidrlist']
|
self.result['cidr'] = firewall_rule['cidrlist']
|
||||||
if 'startport' in firewall_rule:
|
if 'startport' in firewall_rule:
|
||||||
|
@ -281,15 +395,19 @@ class AnsibleCloudStackFirewall(AnsibleCloudStack):
|
||||||
self.result['icmp_code'] = int(firewall_rule['icmpcode'])
|
self.result['icmp_code'] = int(firewall_rule['icmpcode'])
|
||||||
if 'icmptype' in firewall_rule:
|
if 'icmptype' in firewall_rule:
|
||||||
self.result['icmp_type'] = int(firewall_rule['icmptype'])
|
self.result['icmp_type'] = int(firewall_rule['icmptype'])
|
||||||
|
if 'networkid' in firewall_rule:
|
||||||
|
self.result['network'] = self.get_network(key='displaytext', network=firewall_rule['networkid'])
|
||||||
return self.result
|
return self.result
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
module = AnsibleModule(
|
module = AnsibleModule(
|
||||||
argument_spec = dict(
|
argument_spec = dict(
|
||||||
ip_address = dict(required=True),
|
ip_address = dict(default=None),
|
||||||
|
network = dict(default=None),
|
||||||
cidr = dict(default='0.0.0.0/0'),
|
cidr = dict(default='0.0.0.0/0'),
|
||||||
protocol = dict(choices=['tcp', 'udp', 'icmp'], default='tcp'),
|
protocol = dict(choices=['tcp', 'udp', 'icmp', 'all'], default='tcp'),
|
||||||
|
type = dict(choices=['ingress', 'egress'], default='ingress'),
|
||||||
icmp_type = dict(type='int', default=None),
|
icmp_type = dict(type='int', default=None),
|
||||||
icmp_code = dict(type='int', default=None),
|
icmp_code = dict(type='int', default=None),
|
||||||
start_port = dict(type='int', aliases=['port'], default=None),
|
start_port = dict(type='int', aliases=['port'], default=None),
|
||||||
|
@ -298,6 +416,7 @@ def main():
|
||||||
domain = dict(default=None),
|
domain = dict(default=None),
|
||||||
account = dict(default=None),
|
account = dict(default=None),
|
||||||
project = dict(default=None),
|
project = dict(default=None),
|
||||||
|
poll_async = dict(choices=BOOLEANS, default=True),
|
||||||
api_key = dict(default=None),
|
api_key = dict(default=None),
|
||||||
api_secret = dict(default=None, no_log=True),
|
api_secret = dict(default=None, no_log=True),
|
||||||
api_url = dict(default=None),
|
api_url = dict(default=None),
|
||||||
|
@ -306,6 +425,7 @@ def main():
|
||||||
mutually_exclusive = (
|
mutually_exclusive = (
|
||||||
['icmp_type', 'start_port'],
|
['icmp_type', 'start_port'],
|
||||||
['icmp_type', 'end_port'],
|
['icmp_type', 'end_port'],
|
||||||
|
['ip_address', 'network'],
|
||||||
),
|
),
|
||||||
supports_check_mode=True
|
supports_check_mode=True
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in a new issue