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: 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
) )