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