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:
Rene Moser 2015-05-19 09:33:04 +02:00
parent 8147103da9
commit 2da7792ff6

View file

@ -29,20 +29,35 @@ author: '"René Moser (@resmo)" <mail@renemoser.net>'
options:
ip_address:
description:
- Public IP address the rule is assigned to.
required: true
- Public IP address the ingress rule is assigned to.
- 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:
description:
- State of the firewall rule.
required: false
default: 'present'
choices: [ 'present', 'absent' ]
type:
description:
- Type of the firewall rule.
required: false
default: 'ingress'
choices: [ 'ingress', 'egress' ]
protocol:
description:
- Protocol of the firewall rule.
- C(all) is only available if C(type=egress)
required: false
default: 'tcp'
choices: [ 'tcp', 'udp', 'icmp' ]
choices: [ 'tcp', 'udp', 'icmp', 'all' ]
cidr:
description:
- CIDR (full notation) to be used for firewall rule.
@ -84,6 +99,11 @@ options:
- Name of the project the firewall rule is related to.
required: false
default: null
poll_async:
description:
- Poll async jobs until job has finished.
required: false
default: true
extends_documentation_fragment: cloudstack
'''
@ -115,15 +135,37 @@ EXAMPLES = '''
end_port: 8888
cidr: 17.0.0.0/8
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 = '''
---
ip_address:
description: IP address of the rule.
description: IP address of the rule if C(type=ingress)
returned: success
type: string
sample: 10.100.212.10
type:
description: Type of the rule.
returned: success
type: string
sample: ingress
cidr:
description: CIDR of the rule.
returned: success
@ -154,6 +196,11 @@ icmp_type:
returned: success
type: int
sample: 1
network:
description: Name of the network if C(type=egress)
returned: success
type: string
sample: my_network
'''
try:
@ -181,32 +228,46 @@ class AnsibleCloudStackFirewall(AnsibleCloudStack):
def get_firewall_rule(self):
if not self.firewall_rule:
cidr = self.module.params.get('cidr')
protocol = self.module.params.get('protocol')
start_port = self.module.params.get('start_port')
end_port = self.get_end_port()
icmp_code = self.module.params.get('icmp_code')
icmp_type = self.module.params.get('icmp_type')
cidr = self.module.params.get('cidr')
protocol = self.module.params.get('protocol')
start_port = self.module.params.get('start_port')
end_port = self.get_end_port()
icmp_code = self.module.params.get('icmp_code')
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):
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:
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['ipaddressid'] = self.get_ip_address('id')
args['account'] = self.get_account('name')
args['domainid'] = self.get_domain('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:
for rule in firewall_rules['firewallrule']:
type_match = self._type_cidr_match(rule, cidr)
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:
self.firewall_rule = rule
@ -221,6 +282,12 @@ class AnsibleCloudStackFirewall(AnsibleCloudStack):
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):
return protocol == 'icmp' \
and protocol == rule['protocol'] \
@ -232,6 +299,30 @@ class AnsibleCloudStackFirewall(AnsibleCloudStack):
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):
firewall_rule = self.get_firewall_rule()
if not firewall_rule:
@ -244,11 +335,22 @@ class AnsibleCloudStackFirewall(AnsibleCloudStack):
args['endport'] = self.get_end_port()
args['icmptype'] = self.module.params.get('icmp_type')
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:
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
@ -256,17 +358,29 @@ class AnsibleCloudStackFirewall(AnsibleCloudStack):
firewall_rule = self.get_firewall_rule()
if firewall_rule:
self.result['changed'] = True
args = {}
args = {}
args['id'] = firewall_rule['id']
fw_type = self.module.params.get('type')
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
def get_result(self, firewall_rule):
if firewall_rule:
self.result['type'] = self.module.params.get('type')
if 'cidrlist' in firewall_rule:
self.result['cidr'] = firewall_rule['cidrlist']
if 'startport' in firewall_rule:
@ -281,15 +395,19 @@ class AnsibleCloudStackFirewall(AnsibleCloudStack):
self.result['icmp_code'] = int(firewall_rule['icmpcode'])
if 'icmptype' in firewall_rule:
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
def main():
module = AnsibleModule(
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'),
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_code = dict(type='int', default=None),
start_port = dict(type='int', aliases=['port'], default=None),
@ -298,6 +416,7 @@ def main():
domain = dict(default=None),
account = dict(default=None),
project = dict(default=None),
poll_async = dict(choices=BOOLEANS, default=True),
api_key = dict(default=None),
api_secret = dict(default=None, no_log=True),
api_url = dict(default=None),
@ -306,6 +425,7 @@ def main():
mutually_exclusive = (
['icmp_type', 'start_port'],
['icmp_type', 'end_port'],
['ip_address', 'network'],
),
supports_check_mode=True
)