2016-04-25 13:02:59 +00:00
|
|
|
#!/usr/bin/python
|
|
|
|
#
|
|
|
|
# Copyright (c) 2016 Matt Davis, <mdavis@ansible.com>
|
|
|
|
# Chris Houseknecht, <house@redhat.com>
|
|
|
|
#
|
2017-07-27 09:22:17 +00:00
|
|
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
|
|
|
|
|
|
from __future__ import absolute_import, division, print_function
|
|
|
|
__metaclass__ = type
|
|
|
|
|
2016-04-25 13:02:59 +00:00
|
|
|
|
2017-08-16 03:16:38 +00:00
|
|
|
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
2017-03-14 16:07:22 +00:00
|
|
|
'status': ['preview'],
|
2017-08-16 03:16:38 +00:00
|
|
|
'supported_by': 'certified'}
|
2017-03-14 16:07:22 +00:00
|
|
|
|
2016-12-06 10:35:05 +00:00
|
|
|
|
2016-04-25 13:02:59 +00:00
|
|
|
DOCUMENTATION = '''
|
|
|
|
---
|
|
|
|
module: azure_rm_securitygroup
|
|
|
|
version_added: "2.1"
|
|
|
|
short_description: Manage Azure network security groups.
|
|
|
|
description:
|
|
|
|
- Create, update or delete a network security group. A security group contains Access Control List (ACL) rules
|
|
|
|
that allow or deny network traffic to subnets or individual network interfaces. A security group is created
|
|
|
|
with a set of default security rules and an empty set of security rules. Shape traffic flow by adding
|
|
|
|
rules to the empty set of security rules.
|
|
|
|
|
|
|
|
options:
|
|
|
|
default_rules:
|
|
|
|
description:
|
|
|
|
- The set of default rules automatically added to a security group at creation. In general default
|
|
|
|
rules will not be modified. Modify rules to shape the flow of traffic to or from a subnet or NIC. See
|
|
|
|
rules below for the makeup of a rule dict.
|
|
|
|
location:
|
|
|
|
description:
|
|
|
|
- Valid azure location. Defaults to location of the resource group.
|
|
|
|
name:
|
|
|
|
description:
|
|
|
|
- Name of the security group to operate on.
|
|
|
|
purge_default_rules:
|
|
|
|
description:
|
|
|
|
- Remove any existing rules not matching those defined in the default_rules parameter.
|
2018-03-15 21:15:24 +00:00
|
|
|
type: bool
|
|
|
|
default: 'no'
|
2016-04-25 13:02:59 +00:00
|
|
|
purge_rules:
|
|
|
|
description:
|
|
|
|
- Remove any existing rules not matching those defined in the rules parameters.
|
2018-03-15 21:15:24 +00:00
|
|
|
type: bool
|
|
|
|
default: 'no'
|
2016-04-25 13:02:59 +00:00
|
|
|
resource_group:
|
|
|
|
description:
|
|
|
|
- Name of the resource group the security group belongs to.
|
|
|
|
required: true
|
|
|
|
rules:
|
|
|
|
description:
|
|
|
|
- Set of rules shaping traffic flow to or from a subnet or NIC. Each rule is a dictionary.
|
2017-03-09 16:20:25 +00:00
|
|
|
suboptions:
|
2016-04-25 13:02:59 +00:00
|
|
|
name:
|
2017-03-15 16:31:37 +00:00
|
|
|
description:
|
|
|
|
- Unique name for the rule.
|
2016-04-25 13:02:59 +00:00
|
|
|
required: true
|
|
|
|
description:
|
2017-03-15 16:31:37 +00:00
|
|
|
description:
|
|
|
|
- Short description of the rule's purpose.
|
2016-04-25 13:02:59 +00:00
|
|
|
protocol:
|
|
|
|
description: Accepted traffic protocol.
|
|
|
|
choices:
|
|
|
|
- Udp
|
|
|
|
- Tcp
|
|
|
|
- "*"
|
|
|
|
default: "*"
|
|
|
|
source_port_range:
|
2017-03-15 16:31:37 +00:00
|
|
|
description:
|
|
|
|
- Port or range of ports from which traffic originates.
|
2018-05-16 08:28:14 +00:00
|
|
|
- It can accept string type or a list of string type.
|
2016-04-25 13:02:59 +00:00
|
|
|
default: "*"
|
|
|
|
destination_port_range:
|
2017-03-15 16:31:37 +00:00
|
|
|
description:
|
|
|
|
- Port or range of ports to which traffic is headed.
|
2018-05-16 08:28:14 +00:00
|
|
|
- It can accept string type or a list of string type.
|
2016-04-25 13:02:59 +00:00
|
|
|
default: "*"
|
|
|
|
source_address_prefix:
|
2017-03-15 16:31:37 +00:00
|
|
|
description:
|
2017-12-05 00:22:03 +00:00
|
|
|
- The CIDR or source IP range.
|
|
|
|
- Asterix C(*) can also be used to match all source IPs.
|
|
|
|
- Default tags such as C(VirtualNetwork), C(AzureLoadBalancer) and C(Internet) can also be used.
|
|
|
|
- If this is an ingress rule, specifies where network traffic originates from.
|
2018-05-16 08:28:14 +00:00
|
|
|
- It can accept string type or a list of string type.
|
2016-04-25 13:02:59 +00:00
|
|
|
default: "*"
|
|
|
|
destination_address_prefix:
|
2017-03-15 16:31:37 +00:00
|
|
|
description:
|
2017-12-05 00:22:03 +00:00
|
|
|
- The destination address prefix.
|
|
|
|
- CIDR or destination IP range.
|
|
|
|
- Asterix C(*) can also be used to match all source IPs.
|
|
|
|
- Default tags such as C(VirtualNetwork), C(AzureLoadBalancer) and C(Internet) can also be used.
|
2018-05-16 08:28:14 +00:00
|
|
|
- It can accept string type or a list of string type.
|
2016-04-25 13:02:59 +00:00
|
|
|
default: "*"
|
|
|
|
access:
|
2017-03-15 16:31:37 +00:00
|
|
|
description:
|
|
|
|
- Whether or not to allow the traffic flow.
|
2016-04-25 13:02:59 +00:00
|
|
|
choices:
|
|
|
|
- Allow
|
|
|
|
- Deny
|
|
|
|
default: Allow
|
|
|
|
priority:
|
2017-03-15 16:31:37 +00:00
|
|
|
description:
|
|
|
|
- Order in which to apply the rule. Must a unique integer between 100 and 4096 inclusive.
|
2016-04-25 13:02:59 +00:00
|
|
|
required: true
|
|
|
|
direction:
|
2017-03-15 16:31:37 +00:00
|
|
|
description:
|
|
|
|
- Indicates the direction of the traffic flow.
|
2016-04-25 13:02:59 +00:00
|
|
|
choices:
|
|
|
|
- Inbound
|
|
|
|
- Outbound
|
|
|
|
default: Inbound
|
|
|
|
state:
|
|
|
|
description:
|
|
|
|
- Assert the state of the security group. Set to 'present' to create or update a security group. Set to
|
|
|
|
'absent' to remove a security group.
|
|
|
|
default: present
|
|
|
|
choices:
|
|
|
|
- absent
|
|
|
|
- present
|
|
|
|
|
|
|
|
extends_documentation_fragment:
|
|
|
|
- azure
|
2016-04-26 16:46:41 +00:00
|
|
|
- azure_tags
|
2016-04-25 13:02:59 +00:00
|
|
|
|
|
|
|
author:
|
|
|
|
- "Chris Houseknecht (@chouseknecht)"
|
|
|
|
- "Matt Davis (@nitzmahone)"
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
|
|
|
EXAMPLES = '''
|
|
|
|
|
|
|
|
# Create a security group
|
|
|
|
- azure_rm_securitygroup:
|
|
|
|
resource_group: mygroup
|
|
|
|
name: mysecgroup
|
|
|
|
purge_rules: yes
|
|
|
|
rules:
|
|
|
|
- name: DenySSH
|
2018-07-05 09:59:23 +00:00
|
|
|
protocol: Tcp
|
2016-04-25 13:02:59 +00:00
|
|
|
destination_port_range: 22
|
2016-05-03 14:51:12 +00:00
|
|
|
access: Deny
|
2016-04-25 13:02:59 +00:00
|
|
|
priority: 100
|
2016-05-03 14:51:12 +00:00
|
|
|
direction: Inbound
|
2016-04-25 13:02:59 +00:00
|
|
|
- name: 'AllowSSH'
|
2018-07-05 09:59:23 +00:00
|
|
|
protocol: Tcp
|
2018-05-16 08:28:14 +00:00
|
|
|
source_address_prefix:
|
|
|
|
- '174.109.158.0/24'
|
|
|
|
- '174.109.159.0/24'
|
2016-04-25 13:02:59 +00:00
|
|
|
destination_port_range: 22
|
|
|
|
access: Allow
|
|
|
|
priority: 101
|
|
|
|
direction: Inbound
|
2018-07-05 09:59:23 +00:00
|
|
|
- name: 'AllowMultiplePorts'
|
|
|
|
protocol: Tcp
|
|
|
|
source_address_prefix:
|
|
|
|
- '174.109.158.0/24'
|
|
|
|
- '174.109.159.0/24'
|
|
|
|
destination_port_range:
|
|
|
|
- 80
|
|
|
|
- 443
|
|
|
|
access: Allow
|
|
|
|
priority: 102
|
2016-04-25 13:02:59 +00:00
|
|
|
|
|
|
|
# Update rules on existing security group
|
|
|
|
- azure_rm_securitygroup:
|
|
|
|
resource_group: mygroup
|
|
|
|
name: mysecgroup
|
|
|
|
rules:
|
|
|
|
- name: DenySSH
|
2018-07-05 09:59:23 +00:00
|
|
|
protocol: Tcp
|
2016-04-25 13:02:59 +00:00
|
|
|
destination_port_range: 22-23
|
|
|
|
access: Deny
|
|
|
|
priority: 100
|
2016-05-03 14:51:12 +00:00
|
|
|
direction: Inbound
|
2016-04-25 13:02:59 +00:00
|
|
|
- name: AllowSSHFromHome
|
2018-07-05 09:59:23 +00:00
|
|
|
protocol: Tcp
|
2016-04-25 13:02:59 +00:00
|
|
|
source_address_prefix: '174.109.158.0/24'
|
|
|
|
destination_port_range: 22-23
|
|
|
|
access: Allow
|
|
|
|
priority: 102
|
2016-05-03 14:51:12 +00:00
|
|
|
direction: Inbound
|
2016-04-25 13:02:59 +00:00
|
|
|
tags:
|
|
|
|
testing: testing
|
|
|
|
delete: on-exit
|
|
|
|
|
|
|
|
# Delete security group
|
|
|
|
- azure_rm_securitygroup:
|
|
|
|
resource_group: mygroup
|
2016-05-03 14:51:12 +00:00
|
|
|
name: mysecgroup
|
2016-04-25 13:02:59 +00:00
|
|
|
state: absent
|
|
|
|
'''
|
|
|
|
|
|
|
|
RETURN = '''
|
|
|
|
state:
|
2016-04-25 13:43:12 +00:00
|
|
|
description: Current state of the security group.
|
2016-04-25 13:02:59 +00:00
|
|
|
returned: always
|
|
|
|
type: dict
|
|
|
|
sample: {
|
|
|
|
"default_rules": [
|
|
|
|
{
|
|
|
|
"access": "Allow",
|
|
|
|
"description": "Allow inbound traffic from all VMs in VNET",
|
|
|
|
"destination_address_prefix": "VirtualNetwork",
|
|
|
|
"destination_port_range": "*",
|
|
|
|
"direction": "Inbound",
|
2016-05-03 14:51:12 +00:00
|
|
|
"etag": 'W/"edf48d56-b315-40ca-a85d-dbcb47f2da7d"',
|
2016-04-25 13:02:59 +00:00
|
|
|
"id": "/subscriptions/3f7e29ba-24e0-42f6-8d9c-5149a14bda37/resourceGroups/Testing/providers/Microsoft.Network/networkSecurityGroups/mysecgroup/defaultSecurityRules/AllowVnetInBound",
|
|
|
|
"name": "AllowVnetInBound",
|
|
|
|
"priority": 65000,
|
|
|
|
"protocol": "*",
|
|
|
|
"provisioning_state": "Succeeded",
|
|
|
|
"source_address_prefix": "VirtualNetwork",
|
|
|
|
"source_port_range": "*"
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"access": "Allow",
|
|
|
|
"description": "Allow inbound traffic from azure load balancer",
|
|
|
|
"destination_address_prefix": "*",
|
|
|
|
"destination_port_range": "*",
|
|
|
|
"direction": "Inbound",
|
2016-05-03 14:51:12 +00:00
|
|
|
"etag": 'W/"edf48d56-b315-40ca-a85d-dbcb47f2da7d"',
|
2016-04-25 13:02:59 +00:00
|
|
|
"id": "/subscriptions/3f7e29ba-24e0-42f6-8d9c-5149a14bda37/resourceGroups/Testing/providers/Microsoft.Network/networkSecurityGroups/mysecgroup/defaultSecurityRules/AllowAzureLoadBalancerInBound",
|
|
|
|
"name": "AllowAzureLoadBalancerInBound",
|
|
|
|
"priority": 65001,
|
|
|
|
"protocol": "*",
|
|
|
|
"provisioning_state": "Succeeded",
|
|
|
|
"source_address_prefix": "AzureLoadBalancer",
|
|
|
|
"source_port_range": "*"
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"access": "Deny",
|
|
|
|
"description": "Deny all inbound traffic",
|
|
|
|
"destination_address_prefix": "*",
|
|
|
|
"destination_port_range": "*",
|
|
|
|
"direction": "Inbound",
|
2016-05-03 14:51:12 +00:00
|
|
|
"etag": 'W/"edf48d56-b315-40ca-a85d-dbcb47f2da7d"',
|
2016-04-25 13:02:59 +00:00
|
|
|
"id": "/subscriptions/3f7e29ba-24e0-42f6-8d9c-5149a14bda37/resourceGroups/Testing/providers/Microsoft.Network/networkSecurityGroups/mysecgroup/defaultSecurityRules/DenyAllInBound",
|
|
|
|
"name": "DenyAllInBound",
|
|
|
|
"priority": 65500,
|
|
|
|
"protocol": "*",
|
|
|
|
"provisioning_state": "Succeeded",
|
|
|
|
"source_address_prefix": "*",
|
|
|
|
"source_port_range": "*"
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"access": "Allow",
|
|
|
|
"description": "Allow outbound traffic from all VMs to all VMs in VNET",
|
|
|
|
"destination_address_prefix": "VirtualNetwork",
|
|
|
|
"destination_port_range": "*",
|
|
|
|
"direction": "Outbound",
|
2016-05-03 14:51:12 +00:00
|
|
|
"etag": 'W/"edf48d56-b315-40ca-a85d-dbcb47f2da7d"',
|
2016-04-25 13:02:59 +00:00
|
|
|
"id": "/subscriptions/3f7e29ba-24e0-42f6-8d9c-5149a14bda37/resourceGroups/Testing/providers/Microsoft.Network/networkSecurityGroups/mysecgroup/defaultSecurityRules/AllowVnetOutBound",
|
|
|
|
"name": "AllowVnetOutBound",
|
|
|
|
"priority": 65000,
|
|
|
|
"protocol": "*",
|
|
|
|
"provisioning_state": "Succeeded",
|
|
|
|
"source_address_prefix": "VirtualNetwork",
|
|
|
|
"source_port_range": "*"
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"access": "Allow",
|
|
|
|
"description": "Allow outbound traffic from all VMs to Internet",
|
|
|
|
"destination_address_prefix": "Internet",
|
|
|
|
"destination_port_range": "*",
|
|
|
|
"direction": "Outbound",
|
2016-05-03 14:51:12 +00:00
|
|
|
"etag": 'W/"edf48d56-b315-40ca-a85d-dbcb47f2da7d"',
|
2016-04-25 13:02:59 +00:00
|
|
|
"id": "/subscriptions/3f7e29ba-24e0-42f6-8d9c-5149a14bda37/resourceGroups/Testing/providers/Microsoft.Network/networkSecurityGroups/mysecgroup/defaultSecurityRules/AllowInternetOutBound",
|
|
|
|
"name": "AllowInternetOutBound",
|
|
|
|
"priority": 65001,
|
|
|
|
"protocol": "*",
|
|
|
|
"provisioning_state": "Succeeded",
|
|
|
|
"source_address_prefix": "*",
|
|
|
|
"source_port_range": "*"
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"access": "Deny",
|
|
|
|
"description": "Deny all outbound traffic",
|
|
|
|
"destination_address_prefix": "*",
|
|
|
|
"destination_port_range": "*",
|
|
|
|
"direction": "Outbound",
|
2016-05-03 14:51:12 +00:00
|
|
|
"etag": 'W/"edf48d56-b315-40ca-a85d-dbcb47f2da7d"',
|
2016-04-25 13:02:59 +00:00
|
|
|
"id": "/subscriptions/3f7e29ba-24e0-42f6-8d9c-5149a14bda37/resourceGroups/Testing/providers/Microsoft.Network/networkSecurityGroups/mysecgroup/defaultSecurityRules/DenyAllOutBound",
|
|
|
|
"name": "DenyAllOutBound",
|
|
|
|
"priority": 65500,
|
|
|
|
"protocol": "*",
|
|
|
|
"provisioning_state": "Succeeded",
|
|
|
|
"source_address_prefix": "*",
|
|
|
|
"source_port_range": "*"
|
|
|
|
}
|
|
|
|
],
|
|
|
|
"id": "/subscriptions/3f7e29ba-24e0-42f6-8d9c-5149a14bda37/resourceGroups/Testing/providers/Microsoft.Network/networkSecurityGroups/mysecgroup",
|
|
|
|
"location": "westus",
|
|
|
|
"name": "mysecgroup",
|
|
|
|
"network_interfaces": [],
|
|
|
|
"rules": [
|
|
|
|
{
|
|
|
|
"access": "Deny",
|
|
|
|
"description": null,
|
|
|
|
"destination_address_prefix": "*",
|
|
|
|
"destination_port_range": "22",
|
|
|
|
"direction": "Inbound",
|
2016-05-03 14:51:12 +00:00
|
|
|
"etag": 'W/"edf48d56-b315-40ca-a85d-dbcb47f2da7d"',
|
2016-04-25 13:02:59 +00:00
|
|
|
"id": "/subscriptions/3f7e29ba-24e0-42f6-8d9c-5149a14bda37/resourceGroups/Testing/providers/Microsoft.Network/networkSecurityGroups/mysecgroup/securityRules/DenySSH",
|
|
|
|
"name": "DenySSH",
|
|
|
|
"priority": 100,
|
|
|
|
"protocol": "Tcp",
|
|
|
|
"provisioning_state": "Succeeded",
|
|
|
|
"source_address_prefix": "*",
|
|
|
|
"source_port_range": "*"
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"access": "Allow",
|
|
|
|
"description": null,
|
|
|
|
"destination_address_prefix": "*",
|
|
|
|
"destination_port_range": "22",
|
|
|
|
"direction": "Inbound",
|
2016-05-03 14:51:12 +00:00
|
|
|
"etag": 'W/"edf48d56-b315-40ca-a85d-dbcb47f2da7d"',
|
2016-04-25 13:02:59 +00:00
|
|
|
"id": "/subscriptions/3f7e29ba-24e0-42f6-8d9c-5149a14bda37/resourceGroups/Testing/providers/Microsoft.Network/networkSecurityGroups/mysecgroup/securityRules/AllowSSH",
|
|
|
|
"name": "AllowSSH",
|
|
|
|
"priority": 101,
|
|
|
|
"protocol": "Tcp",
|
|
|
|
"provisioning_state": "Succeeded",
|
|
|
|
"source_address_prefix": "174.109.158.0/24",
|
|
|
|
"source_port_range": "*"
|
|
|
|
}
|
|
|
|
],
|
|
|
|
"subnets": [],
|
|
|
|
"tags": {
|
|
|
|
"delete": "on-exit",
|
|
|
|
"foo": "bar",
|
|
|
|
"testing": "testing"
|
|
|
|
},
|
|
|
|
"type": "Microsoft.Network/networkSecurityGroups"
|
|
|
|
}
|
2017-03-23 01:50:28 +00:00
|
|
|
''' # NOQA
|
2016-04-25 13:02:59 +00:00
|
|
|
|
|
|
|
try:
|
|
|
|
from msrestazure.azure_exceptions import CloudError
|
2018-02-09 22:26:42 +00:00
|
|
|
from azure.mgmt.network import NetworkManagementClient
|
2016-04-25 13:02:59 +00:00
|
|
|
except ImportError:
|
|
|
|
# This is handled in azure_rm_common
|
|
|
|
pass
|
|
|
|
|
2017-07-27 09:22:17 +00:00
|
|
|
from ansible.module_utils.azure_rm_common import AzureRMModuleBase
|
|
|
|
from ansible.module_utils.six import integer_types
|
2018-08-08 03:41:46 +00:00
|
|
|
from ansible.module_utils._text import to_native
|
2017-07-27 09:22:17 +00:00
|
|
|
|
2016-04-25 13:02:59 +00:00
|
|
|
|
2017-12-28 08:27:13 +00:00
|
|
|
def validate_rule(self, rule, rule_type=None):
|
2016-04-25 13:02:59 +00:00
|
|
|
'''
|
|
|
|
Apply defaults to a rule dictionary and check that all values are valid.
|
|
|
|
|
|
|
|
:param rule: rule dict
|
|
|
|
:param rule_type: Set to 'default' if the rule is part of the default set of rules.
|
|
|
|
:return: None
|
|
|
|
'''
|
2018-05-16 08:28:14 +00:00
|
|
|
priority = rule.get('priority', 0)
|
2016-04-25 13:02:59 +00:00
|
|
|
if rule_type != 'default' and (priority < 100 or priority > 4096):
|
|
|
|
raise Exception("Rule priority must be between 100 and 4096")
|
|
|
|
|
2018-05-16 08:28:14 +00:00
|
|
|
def check_plural(src, dest):
|
|
|
|
if isinstance(rule.get(src), list):
|
|
|
|
rule[dest] = rule[src]
|
|
|
|
rule[src] = None
|
2016-04-25 13:02:59 +00:00
|
|
|
|
2018-05-16 08:28:14 +00:00
|
|
|
check_plural('destination_address_prefix', 'destination_address_prefixes')
|
|
|
|
check_plural('source_address_prefix', 'source_address_prefixes')
|
|
|
|
check_plural('source_port_range', 'source_port_ranges')
|
|
|
|
check_plural('destination_port_range', 'destination_port_ranges')
|
2016-04-25 13:02:59 +00:00
|
|
|
|
|
|
|
|
2018-05-16 08:28:14 +00:00
|
|
|
def compare_rules_change(old_list, new_list, purge_list):
|
|
|
|
old_list = old_list or []
|
|
|
|
new_list = new_list or []
|
|
|
|
changed = False
|
2016-04-25 13:02:59 +00:00
|
|
|
|
2018-05-16 08:28:14 +00:00
|
|
|
for old_rule in old_list:
|
|
|
|
matched = next((x for x in new_list if x['name'] == old_rule['name']), [])
|
|
|
|
if matched: # if the new one is in the old list, check whether it is updated
|
|
|
|
changed = changed or compare_rules(old_rule, matched)
|
|
|
|
elif not purge_list: # keep this rule
|
|
|
|
new_list.append(old_rule)
|
|
|
|
else: # one rule is removed
|
|
|
|
changed = True
|
2018-08-08 03:41:46 +00:00
|
|
|
# Compare new list and old list is the same? here only compare names
|
|
|
|
if not changed:
|
|
|
|
new_names = [to_native(x['name']) for x in new_list]
|
|
|
|
old_names = [to_native(x['name']) for x in old_list]
|
|
|
|
changed = (set(new_names) != set(old_names))
|
2018-05-16 08:28:14 +00:00
|
|
|
return changed, new_list
|
2016-04-25 13:02:59 +00:00
|
|
|
|
|
|
|
|
2018-05-16 08:28:14 +00:00
|
|
|
def compare_rules(old_rule, rule):
|
2016-04-25 13:02:59 +00:00
|
|
|
changed = False
|
2018-05-16 08:28:14 +00:00
|
|
|
if old_rule['name'] != rule['name']:
|
|
|
|
changed = True
|
|
|
|
if rule.get('description', None) != old_rule['description']:
|
|
|
|
changed = True
|
|
|
|
if rule['protocol'] != old_rule['protocol']:
|
|
|
|
changed = True
|
|
|
|
if str(rule['source_port_range']) != str(old_rule['source_port_range']):
|
|
|
|
changed = True
|
|
|
|
if str(rule['destination_port_range']) != str(old_rule['destination_port_range']):
|
|
|
|
changed = True
|
|
|
|
if rule['access'] != old_rule['access']:
|
|
|
|
changed = True
|
|
|
|
if rule['priority'] != old_rule['priority']:
|
|
|
|
changed = True
|
|
|
|
if rule['direction'] != old_rule['direction']:
|
|
|
|
changed = True
|
|
|
|
if str(rule['source_address_prefix']) != str(old_rule['source_address_prefix']):
|
|
|
|
changed = True
|
|
|
|
if str(rule['destination_address_prefix']) != str(old_rule['destination_address_prefix']):
|
|
|
|
changed = True
|
|
|
|
if set(rule.get('source_address_prefixes') or []) != set(old_rule.get('source_address_prefixes') or []):
|
|
|
|
changed = True
|
|
|
|
if set(rule.get('destination_address_prefixes') or []) != set(old_rule.get('destination_address_prefixes') or []):
|
|
|
|
changed = True
|
|
|
|
if set(rule.get('source_port_ranges') or []) != set(old_rule.get('source_port_ranges') or []):
|
|
|
|
changed = True
|
|
|
|
if set(rule.get('destination_port_ranges') or []) != set(old_rule.get('destination_port_ranges') or []):
|
|
|
|
changed = True
|
|
|
|
return changed
|
2016-04-25 13:02:59 +00:00
|
|
|
|
|
|
|
|
2017-12-28 08:27:13 +00:00
|
|
|
def create_rule_instance(self, rule):
|
2016-04-25 13:02:59 +00:00
|
|
|
'''
|
|
|
|
Create an instance of SecurityRule from a dict.
|
|
|
|
|
|
|
|
:param rule: dict
|
|
|
|
:return: SecurityRule
|
|
|
|
'''
|
2018-02-09 22:26:42 +00:00
|
|
|
return self.nsg_models.SecurityRule(
|
2016-04-25 13:02:59 +00:00
|
|
|
description=rule.get('description', None),
|
2018-05-16 08:28:14 +00:00
|
|
|
protocol=rule.get('protocol', None),
|
2016-04-25 13:02:59 +00:00
|
|
|
source_port_range=rule.get('source_port_range', None),
|
|
|
|
destination_port_range=rule.get('destination_port_range', None),
|
2018-05-16 08:28:14 +00:00
|
|
|
source_address_prefix=rule.get('source_address_prefix', None),
|
|
|
|
source_address_prefixes=rule.get('source_address_prefixes', None),
|
|
|
|
destination_address_prefix=rule.get('destination_address_prefix', None),
|
|
|
|
destination_address_prefixes=rule.get('destination_address_prefixes', None),
|
|
|
|
source_port_ranges=rule.get('source_port_ranges', None),
|
|
|
|
destination_port_ranges=rule.get('destination_port_ranges', None),
|
|
|
|
access=rule.get('access', None),
|
2016-04-25 13:02:59 +00:00
|
|
|
priority=rule.get('priority', None),
|
2018-05-16 08:28:14 +00:00
|
|
|
direction=rule.get('direction', None),
|
2016-04-25 13:02:59 +00:00
|
|
|
provisioning_state=rule.get('provisioning_state', None),
|
2016-05-03 14:51:12 +00:00
|
|
|
name=rule.get('name', None),
|
2016-04-25 13:02:59 +00:00
|
|
|
etag=rule.get('etag', None)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def create_rule_dict_from_obj(rule):
|
|
|
|
'''
|
|
|
|
Create a dict from an instance of a SecurityRule.
|
|
|
|
|
|
|
|
:param rule: SecurityRule
|
|
|
|
:return: dict
|
|
|
|
'''
|
|
|
|
return dict(
|
|
|
|
id=rule.id,
|
|
|
|
name=rule.name,
|
|
|
|
description=rule.description,
|
2016-06-29 07:19:59 +00:00
|
|
|
protocol=rule.protocol,
|
2016-04-25 13:02:59 +00:00
|
|
|
source_port_range=rule.source_port_range,
|
|
|
|
destination_port_range=rule.destination_port_range,
|
|
|
|
source_address_prefix=rule.source_address_prefix,
|
|
|
|
destination_address_prefix=rule.destination_address_prefix,
|
2018-05-16 08:28:14 +00:00
|
|
|
source_port_ranges=rule.source_port_ranges,
|
|
|
|
destination_port_ranges=rule.destination_port_ranges,
|
|
|
|
source_address_prefixes=rule.source_address_prefixes,
|
|
|
|
destination_address_prefixes=rule.destination_address_prefixes,
|
2016-06-29 07:19:59 +00:00
|
|
|
access=rule.access,
|
2016-04-25 13:02:59 +00:00
|
|
|
priority=rule.priority,
|
2016-06-29 07:19:59 +00:00
|
|
|
direction=rule.direction,
|
2016-04-25 13:02:59 +00:00
|
|
|
provisioning_state=rule.provisioning_state,
|
|
|
|
etag=rule.etag
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def create_network_security_group_dict(nsg):
|
|
|
|
results = dict(
|
|
|
|
id=nsg.id,
|
|
|
|
name=nsg.name,
|
|
|
|
type=nsg.type,
|
|
|
|
location=nsg.location,
|
2016-05-03 14:51:12 +00:00
|
|
|
tags=nsg.tags,
|
2016-04-25 13:02:59 +00:00
|
|
|
)
|
|
|
|
results['rules'] = []
|
|
|
|
if nsg.security_rules:
|
|
|
|
for rule in nsg.security_rules:
|
|
|
|
results['rules'].append(create_rule_dict_from_obj(rule))
|
|
|
|
|
|
|
|
results['default_rules'] = []
|
|
|
|
if nsg.default_security_rules:
|
|
|
|
for rule in nsg.default_security_rules:
|
|
|
|
results['default_rules'].append(create_rule_dict_from_obj(rule))
|
|
|
|
|
|
|
|
results['network_interfaces'] = []
|
|
|
|
if nsg.network_interfaces:
|
|
|
|
for interface in nsg.network_interfaces:
|
|
|
|
results['network_interfaces'].append(interface.id)
|
|
|
|
|
|
|
|
results['subnets'] = []
|
|
|
|
if nsg.subnets:
|
|
|
|
for subnet in nsg.subnets:
|
|
|
|
results['subnets'].append(subnet.id)
|
|
|
|
|
|
|
|
return results
|
|
|
|
|
|
|
|
|
2018-05-16 08:28:14 +00:00
|
|
|
rule_spec = dict(
|
|
|
|
name=dict(type='str', required=True),
|
|
|
|
description=dict(type='str'),
|
|
|
|
protocol=dict(type='str', choices=['Udp', 'Tcp', '*'], default='*'),
|
|
|
|
source_port_range=dict(type='raw', default='*'),
|
|
|
|
destination_port_range=dict(type='raw', default='*'),
|
|
|
|
source_address_prefix=dict(type='raw', default='*'),
|
|
|
|
destination_address_prefix=dict(type='raw', default='*'),
|
|
|
|
access=dict(type='str', choices=['Allow', 'Deny'], default='Allow'),
|
|
|
|
priority=dict(type='int', required=True),
|
|
|
|
direction=dict(type='str', choices=['Inbound', 'Outbound'], default='Inbound')
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2016-04-25 13:02:59 +00:00
|
|
|
class AzureRMSecurityGroup(AzureRMModuleBase):
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
|
|
|
|
self.module_arg_spec = dict(
|
2018-05-16 08:28:14 +00:00
|
|
|
default_rules=dict(type='list', elements='dict', options=rule_spec),
|
2016-04-25 13:02:59 +00:00
|
|
|
location=dict(type='str'),
|
|
|
|
name=dict(type='str', required=True),
|
|
|
|
purge_default_rules=dict(type='bool', default=False),
|
|
|
|
purge_rules=dict(type='bool', default=False),
|
|
|
|
resource_group=dict(required=True, type='str'),
|
2018-05-16 08:28:14 +00:00
|
|
|
rules=dict(type='list', elements='dict', options=rule_spec),
|
2016-04-25 13:02:59 +00:00
|
|
|
state=dict(type='str', default='present', choices=['present', 'absent']),
|
|
|
|
)
|
|
|
|
|
|
|
|
self.default_rules = None
|
|
|
|
self.location = None
|
|
|
|
self.name = None
|
|
|
|
self.purge_default_rules = None
|
|
|
|
self.purge_rules = None
|
|
|
|
self.resource_group = None
|
|
|
|
self.rules = None
|
|
|
|
self.state = None
|
|
|
|
self.tags = None
|
2018-02-09 22:26:42 +00:00
|
|
|
self.nsg_models = None # type: azure.mgmt.network.models
|
2016-04-25 13:02:59 +00:00
|
|
|
|
|
|
|
self.results = dict(
|
|
|
|
changed=False,
|
|
|
|
state=dict()
|
|
|
|
)
|
|
|
|
|
|
|
|
super(AzureRMSecurityGroup, self).__init__(self.module_arg_spec,
|
|
|
|
supports_check_mode=True)
|
|
|
|
|
|
|
|
def exec_module(self, **kwargs):
|
2018-02-09 22:26:42 +00:00
|
|
|
# tighten up poll interval for security groups; default 30s is an eternity
|
|
|
|
# this value is still overridden by the response Retry-After header (which is set on the initial operation response to 10s)
|
2018-06-05 21:41:29 +00:00
|
|
|
self.network_client.config.long_running_operation_timeout = 3
|
|
|
|
self.nsg_models = self.network_client.network_security_groups.models
|
2016-05-03 14:51:12 +00:00
|
|
|
|
2017-06-26 06:13:28 +00:00
|
|
|
for key in list(self.module_arg_spec.keys()) + ['tags']:
|
2016-04-25 13:02:59 +00:00
|
|
|
setattr(self, key, kwargs[key])
|
|
|
|
|
|
|
|
changed = False
|
|
|
|
results = dict()
|
|
|
|
|
|
|
|
resource_group = self.get_resource_group(self.resource_group)
|
|
|
|
if not self.location:
|
|
|
|
# Set default location
|
|
|
|
self.location = resource_group.location
|
|
|
|
|
|
|
|
if self.rules:
|
|
|
|
for rule in self.rules:
|
|
|
|
try:
|
2017-12-28 08:27:13 +00:00
|
|
|
validate_rule(self, rule)
|
2016-04-25 13:02:59 +00:00
|
|
|
except Exception as exc:
|
|
|
|
self.fail("Error validating rule {0} - {1}".format(rule, str(exc)))
|
|
|
|
|
|
|
|
if self.default_rules:
|
|
|
|
for rule in self.default_rules:
|
|
|
|
try:
|
2017-12-28 08:27:13 +00:00
|
|
|
validate_rule(self, rule, 'default')
|
2016-04-25 13:02:59 +00:00
|
|
|
except Exception as exc:
|
|
|
|
self.fail("Error validating default rule {0} - {1}".format(rule, str(exc)))
|
|
|
|
|
|
|
|
try:
|
2018-06-05 21:41:29 +00:00
|
|
|
nsg = self.network_client.network_security_groups.get(self.resource_group, self.name)
|
2016-04-25 13:02:59 +00:00
|
|
|
results = create_network_security_group_dict(nsg)
|
|
|
|
self.log("Found security group:")
|
|
|
|
self.log(results, pretty_print=True)
|
|
|
|
self.check_provisioning_state(nsg, self.state)
|
|
|
|
if self.state == 'present':
|
|
|
|
pass
|
|
|
|
elif self.state == 'absent':
|
|
|
|
self.log("CHANGED: security group found but state is 'absent'")
|
|
|
|
changed = True
|
2018-02-09 22:26:42 +00:00
|
|
|
except CloudError: # TODO: actually check for ResourceMissingError
|
2016-04-25 13:02:59 +00:00
|
|
|
if self.state == 'present':
|
|
|
|
self.log("CHANGED: security group not found and state is 'present'")
|
|
|
|
changed = True
|
|
|
|
|
|
|
|
if self.state == 'present' and not changed:
|
|
|
|
# update the security group
|
|
|
|
self.log("Update security group {0}".format(self.name))
|
|
|
|
|
|
|
|
update_tags, results['tags'] = self.update_tags(results['tags'])
|
|
|
|
if update_tags:
|
|
|
|
changed = True
|
|
|
|
|
2018-05-16 08:28:14 +00:00
|
|
|
rule_changed, new_rule = compare_rules_change(results['rules'], self.rules, self.purge_rules)
|
|
|
|
if rule_changed:
|
|
|
|
changed = True
|
|
|
|
results['rules'] = new_rule
|
|
|
|
rule_changed, new_rule = compare_rules_change(results['default_rules'], self.default_rules, self.purge_default_rules)
|
|
|
|
if rule_changed:
|
|
|
|
changed = True
|
|
|
|
results['default_rules'] = new_rule
|
|
|
|
|
2016-04-25 13:02:59 +00:00
|
|
|
self.results['changed'] = changed
|
|
|
|
self.results['state'] = results
|
2018-02-09 22:26:42 +00:00
|
|
|
if not self.check_mode and changed:
|
2016-04-25 13:02:59 +00:00
|
|
|
self.results['state'] = self.create_or_update(results)
|
|
|
|
|
|
|
|
elif self.state == 'present' and changed:
|
|
|
|
# create the security group
|
|
|
|
self.log("Create security group {0}".format(self.name))
|
|
|
|
|
|
|
|
if not self.location:
|
|
|
|
self.fail("Parameter error: location required when creating a security group.")
|
|
|
|
|
|
|
|
results['name'] = self.name
|
|
|
|
results['location'] = self.location
|
|
|
|
results['rules'] = []
|
|
|
|
results['default_rules'] = []
|
|
|
|
results['tags'] = {}
|
|
|
|
|
|
|
|
if self.rules:
|
|
|
|
results['rules'] = self.rules
|
|
|
|
if self.default_rules:
|
|
|
|
results['default_rules'] = self.default_rules
|
|
|
|
if self.tags:
|
|
|
|
results['tags'] = self.tags
|
|
|
|
|
|
|
|
self.results['changed'] = changed
|
|
|
|
self.results['state'] = results
|
|
|
|
if not self.check_mode:
|
|
|
|
self.results['state'] = self.create_or_update(results)
|
|
|
|
|
|
|
|
elif self.state == 'absent' and changed:
|
|
|
|
self.log("Delete security group {0}".format(self.name))
|
|
|
|
self.results['changed'] = changed
|
|
|
|
self.results['state'] = dict()
|
|
|
|
if not self.check_mode:
|
|
|
|
self.delete()
|
|
|
|
# the delete does not actually return anything. if no exception, then we'll assume
|
|
|
|
# it worked.
|
|
|
|
self.results['state']['status'] = 'Deleted'
|
|
|
|
|
|
|
|
return self.results
|
|
|
|
|
|
|
|
def create_or_update(self, results):
|
2018-02-09 22:26:42 +00:00
|
|
|
parameters = self.nsg_models.NetworkSecurityGroup()
|
2016-04-25 13:02:59 +00:00
|
|
|
if results.get('rules'):
|
|
|
|
parameters.security_rules = []
|
|
|
|
for rule in results.get('rules'):
|
2017-12-28 08:27:13 +00:00
|
|
|
parameters.security_rules.append(create_rule_instance(self, rule))
|
2016-04-25 13:02:59 +00:00
|
|
|
if results.get('default_rules'):
|
|
|
|
parameters.default_security_rules = []
|
|
|
|
for rule in results.get('default_rules'):
|
2017-12-28 08:27:13 +00:00
|
|
|
parameters.default_security_rules.append(create_rule_instance(self, rule))
|
2016-04-25 13:02:59 +00:00
|
|
|
parameters.tags = results.get('tags')
|
|
|
|
parameters.location = results.get('location')
|
|
|
|
|
|
|
|
try:
|
2018-06-05 21:41:29 +00:00
|
|
|
poller = self.network_client.network_security_groups.create_or_update(resource_group_name=self.resource_group,
|
|
|
|
network_security_group_name=self.name,
|
|
|
|
parameters=parameters)
|
2016-04-25 13:02:59 +00:00
|
|
|
result = self.get_poller_result(poller)
|
2017-08-11 15:22:02 +00:00
|
|
|
except CloudError as exc:
|
2017-06-01 09:45:19 +00:00
|
|
|
self.fail("Error creating/updating security group {0} - {1}".format(self.name, str(exc)))
|
2016-04-25 13:02:59 +00:00
|
|
|
return create_network_security_group_dict(result)
|
|
|
|
|
|
|
|
def delete(self):
|
|
|
|
try:
|
2018-06-05 21:41:29 +00:00
|
|
|
poller = self.network_client.network_security_groups.delete(resource_group_name=self.resource_group, network_security_group_name=self.name)
|
2016-04-25 13:02:59 +00:00
|
|
|
result = self.get_poller_result(poller)
|
2017-08-11 15:22:02 +00:00
|
|
|
except CloudError as exc:
|
2016-04-25 13:02:59 +00:00
|
|
|
raise Exception("Error deleting security group {0} - {1}".format(self.name, str(exc)))
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
AzureRMSecurityGroup()
|
|
|
|
|
2017-07-23 01:15:46 +00:00
|
|
|
|
2016-04-25 13:02:59 +00:00
|
|
|
if __name__ == '__main__':
|
|
|
|
main()
|