Source code for c7n_azure.resources.network_security_group

# Copyright 2018 Capital One Services, LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import uuid

from c7n.actions import BaseAction
from c7n.filters import Filter, FilterValidationError
from c7n.filters.core import PolicyValidationError
from c7n.utils import type_schema

from c7n_azure.provider import resources
from c7n_azure.resources.arm import ArmResourceManager
from c7n_azure.utils import StringUtils, PortsRangeHelper

from msrestazure.azure_exceptions import CloudError


[docs]@resources.register('networksecuritygroup') class NetworkSecurityGroup(ArmResourceManager):
[docs] class resource_type(object): service = 'azure.mgmt.network' client = 'NetworkManagementClient' enum_spec = ('network_security_groups', 'list_all', None) id = 'id' name = 'name' default_report_fields = ( 'name', 'location', 'resourceGroup' )
DIRECTION = 'direction' PORTS = 'ports' MATCH = 'match' EXCEPT_PORTS = 'exceptPorts' IP_PROTOCOL = 'ipProtocol' ACCESS = 'access' ALLOW_OPERATION = 'Allow' DENY_OPERATION = 'Deny' PRIORITY_STEP = 10
[docs]class NetworkSecurityGroupFilter(Filter): """ Filter Network Security Groups using opened/closed ports configuration """ schema = { 'type': 'object', 'properties': { 'type': {'enum': []}, MATCH: {'type': 'string', 'enum': ['all', 'any']}, PORTS: {'type': 'string'}, EXCEPT_PORTS: {'type': 'string'}, IP_PROTOCOL: {'type': 'string', 'enum': ['TCP', 'UDP', '*']}, ACCESS: {'type': 'string', 'enum': [ALLOW_OPERATION, DENY_OPERATION]}, }, 'required': ['type', ACCESS] }
[docs] def validate(self): # Check that variable values are valid if PORTS in self.data: if not PortsRangeHelper.validate_ports_string(self.data[PORTS]): raise FilterValidationError("ports string has wrong format.") if EXCEPT_PORTS in self.data: if not PortsRangeHelper.validate_ports_string(self.data[EXCEPT_PORTS]): raise FilterValidationError("exceptPorts string has wrong format.") return True
[docs] def process(self, network_security_groups, event=None): # Get variables self.ip_protocol = self.data.get(IP_PROTOCOL, '*') self.IsAllowed = StringUtils.equal(self.data.get(ACCESS), ALLOW_OPERATION) self.match = self.data.get(MATCH, 'all') # Calculate ports from the settings: # If ports not specified -- assuming the entire range # If except_ports not specifed -- nothing ports_set = PortsRangeHelper.get_ports_set_from_string(self.data.get(PORTS, '0-65535')) except_set = PortsRangeHelper.get_ports_set_from_string(self.data.get(EXCEPT_PORTS, '')) self.ports = ports_set.difference(except_set) nsgs = [nsg for nsg in network_security_groups if self._check_nsg(nsg)] return nsgs
def _check_nsg(self, nsg): nsg_ports = PortsRangeHelper.build_ports_dict(nsg, self.direction_key, self.ip_protocol) num_allow_ports = len([p for p in self.ports if nsg_ports.get(p)]) num_deny_ports = len(self.ports) - num_allow_ports if self.match == 'all': if self.IsAllowed: return num_deny_ports == 0 else: return num_allow_ports == 0 if self.match == 'any': if self.IsAllowed: return num_allow_ports > 0 else: return num_deny_ports > 0
[docs]@NetworkSecurityGroup.filter_registry.register('ingress') class IngressFilter(NetworkSecurityGroupFilter): direction_key = 'Inbound' schema = type_schema('ingress', rinherit=NetworkSecurityGroupFilter.schema)
[docs]@NetworkSecurityGroup.filter_registry.register('egress') class EgressFilter(NetworkSecurityGroupFilter): direction_key = 'Outbound' schema = type_schema('egress', rinherit=NetworkSecurityGroupFilter.schema)
[docs]class NetworkSecurityGroupPortsAction(BaseAction): """ Action to perform on Network Security Groups """ schema = { 'type': 'object', 'properties': { 'type': {'enum': []}, PORTS: {'type': 'string'}, EXCEPT_PORTS: {'type': 'string'}, IP_PROTOCOL: {'type': 'string', 'enum': ['TCP', 'UDP', '*']}, DIRECTION: {'type': 'string', 'enum': ['Inbound', 'Outbound']} }, 'required': ['type', DIRECTION] }
[docs] def validate(self): # Check that variable values are valid if PORTS in self.data: if not PortsRangeHelper.validate_ports_string(self.data[PORTS]): raise PolicyValidationError("ports string has wrong format.") if EXCEPT_PORTS in self.data: if not PortsRangeHelper.validate_ports_string(self.data[EXCEPT_PORTS]): raise PolicyValidationError("exceptPorts string has wrong format.") return True
def _build_ports_strings(self, nsg, direction_key, ip_protocol): nsg_ports = PortsRangeHelper.build_ports_dict(nsg, direction_key, ip_protocol) IsAllowed = StringUtils.equal(self.access_action, ALLOW_OPERATION) # Find ports with different access level from NSG and this action diff_ports = sorted([p for p in self.action_ports if nsg_ports.get(p, False) != IsAllowed]) return PortsRangeHelper.get_ports_strings_from_list(diff_ports)
[docs] def process(self, network_security_groups): ip_protocol = self.data.get(IP_PROTOCOL, '*') direction = self.data[DIRECTION] # Build a list of ports described in the action. ports = PortsRangeHelper.get_ports_set_from_string(self.data.get(PORTS, '0-65535')) except_ports = PortsRangeHelper.get_ports_set_from_string(self.data.get(EXCEPT_PORTS, '')) self.action_ports = ports.difference(except_ports) for nsg in network_security_groups: nsg_name = nsg['name'] resource_group = nsg['resourceGroup'] # Get list of ports to Deny or Allow access to. ports = self._build_ports_strings(nsg, direction, ip_protocol) if not ports: # If its empty, it means NSG already blocks/allows access to all ports, # no need to change. self.manager.log.info("Network security group %s satisfies provided " "ports configuration, no actions scheduled.", nsg_name) continue rules = nsg['properties']['securityRules'] rules = sorted(rules, key=lambda k: k['properties']['priority']) rules = [r for r in rules if StringUtils.equal(r['properties']['direction'], direction)] lowest_priority = rules[0]['properties']['priority'] if len(rules) > 0 else 4096 # Create new top-priority rule to allow/block ports from the action. rule_name = 'c7n-policy-' + str(uuid.uuid1()) new_rule = { 'name': rule_name, 'properties': { 'access': self.access_action, 'destinationAddressPrefix': '*', 'destinationPortRanges': ports, 'direction': self.data[DIRECTION], 'priority': lowest_priority - PRIORITY_STEP, 'protocol': ip_protocol, 'sourceAddressPrefix': '*', 'sourcePortRange': '*', } } self.manager.log.info("NSG %s. Creating new rule to %s access for ports %s", nsg_name, self.access_action, ports) try: self.manager.get_client().security_rules.create_or_update( resource_group, nsg_name, rule_name, new_rule ) except CloudError as e: self.manager.log.error('Failed to create or update security rule for %s NSG.', nsg_name) self.manager.log.error(e)
[docs]@NetworkSecurityGroup.action_registry.register('close') class CloseRules(NetworkSecurityGroupPortsAction): """ Deny access to Security Rule """ schema = type_schema('close', rinherit=NetworkSecurityGroupPortsAction.schema) access_action = DENY_OPERATION
[docs]@NetworkSecurityGroup.action_registry.register('open') class OpenRules(NetworkSecurityGroupPortsAction): """ Allow access to Security Rule """ schema = type_schema('open', rinherit=NetworkSecurityGroupPortsAction.schema) access_action = ALLOW_OPERATION