def _get_tag_value_from_event(self, event): principal_role = self.principal_role_jmes_path.search(event) principal_type = self.principal_type_jmes_path.search(event) user = None # The Subscription Admins role does not have a principal type if StringUtils.equal(principal_role, 'Subscription Admin'): user = self.service_admin_jmes_path.search(event) # ServicePrincipal type elif StringUtils.equal(principal_type, 'ServicePrincipal'): user = self.sp_jmes_path.search(event) # Other types and main fallback (e.g. User, Office 365 Groups, and Security Groups) if not user and self.upn_jmes_path.search(event): user = self.upn_jmes_path.search(event) # Last effort search for an email address in the claims if not user: claims = event['data']['claims'] for c in claims: value = claims[c] if self._is_email(value): user = value if not user: self.log.error('Principal could not be determined.') return user
def test_naming_hash(self): source = 'Lorem ipsum dolor sit amet' source2 = 'amet sit dolor ipsum Lorem' self.assertEqual(StringUtils.naming_hash(source), '16aba539') self.assertEqual(StringUtils.naming_hash(source, 10), '16aba5393a') self.assertNotEqual(StringUtils.naming_hash(source), StringUtils.naming_hash(source2))
def _process_resource_set(self, resources, event=None): client = self.manager.get_client() result = [] for resource in resources: database_name = resource['name'] if StringUtils.equal(database_name, "master"): continue if 'c7n:data-masking-policy' not in resource: server_id = resource[ChildTypeInfo.parent_key] server_name = ResourceIdParser.get_resource_name(server_id) dmr = client.data_masking_policies.get( resource['resourceGroup'], server_name, database_name) if dmr: resource['c7n:data-masking-policy'] = dmr.serialize( True).get('properties', {}) else: resource['c7n:data-masking-policy'] = {} required_status = 'Enabled' if self.enabled else 'Disabled' if StringUtils.equal( resource['c7n:data-masking-policy'].get( 'dataMaskingState'), required_status): result.append(resource) return result
def _get_user_from_event(self, event): principal_role = self.principal_role_jmes_path.search(event) principal_type = self.principal_type_jmes_path.search(event) user = None # The Subscription Admins role does not have a principal type if StringUtils.equal(principal_role, 'Subscription Admin'): user = self.service_admin_jmes_path.search(event) # ServicePrincipal type elif StringUtils.equal(principal_type, 'ServicePrincipal'): user = self.sp_jmes_path.search(event) # Other types and main fallback (e.g. User, Office 365 Groups, and Security Groups) if not user and self.upn_jmes_path.search(event): user = self.upn_jmes_path.search(event) # Last effort search for an email address in the claims if not user: claims = event['data']['claims'] for c in claims: value = claims[c] if self._is_email(value): user = value if not user: self.log.error('Principal could not be determined.') return user
def _get_tag_value_from_event(self, event): principal_role = self.principal_role_jmes_path.search(event) principal_type = self.principal_type_jmes_path.search(event) user = None # The Subscription Admins role does not have a principal type if StringUtils.equal(principal_role, 'Subscription Admin'): user = self.service_admin_jmes_path.search(event) # ServicePrincipal type elif StringUtils.equal(principal_type, 'ServicePrincipal'): user = self.sp_jmes_path.search(event) if not user: known_claims = { 'upn': self.upn_jmes_path.search(event), 'name': self.name_jmes_path.search(event) } if known_claims[self.default_claim]: user = known_claims[self.default_claim] elif self.default_claim == 'upn' and known_claims['name']: user = known_claims['name'] elif self.default_claim == 'name' and known_claims['upn']: user = known_claims['upn'] # Last effort search for an email address in the claims if not user: claims = event['data'].get('claims', []) for c in claims: value = claims[c] if self._is_email(value): user = value break if not user: self.log.error('Principal could not be determined.') return user
def _process_resource_set(self, resources, event=None): client = self.manager.get_client( 'azure.mgmt.resource.locks.ManagementLockClient') result = [] for resource in resources: if resource.get('resourceGroup') is None: locks = [ r.serialize(True) for r in client.management_locks. list_at_resource_group_level(resource['name']) ] else: locks = [ r.serialize(True) for r in client.management_locks.list_at_resource_level( resource['resourceGroup'], ResourceIdParser.get_namespace(resource['id']), ResourceIdParser.get_resource_name( resource.get('c7n:parent-id')) or '', ResourceIdParser.get_resource_type(resource['id']), resource['name']) ] if StringUtils.equal('Absent', self.lock_type) and not locks: result.append(resource) else: for lock in locks: if StringUtils.equal('Any', self.lock_type) or \ StringUtils.equal(lock['properties']['level'], self.lock_type): result.append(resource) break return result
def get_function_app_params(self): session = local_session(self.policy.session_factory) provision_options = self.policy.data['mode'].get('provision-options', {}) # Service plan is parsed first, location might be shared with storage & insights service_plan = AzureFunctionMode.extract_properties( provision_options, 'servicePlan', { 'name': 'cloud-custodian', 'location': 'eastus', 'resource_group_name': 'cloud-custodian', 'sku_tier': 'Dynamic', # consumption plan 'sku_name': 'Y1' }) # Metadata used for automatic naming location = service_plan.get('location', 'eastus') rg_name = service_plan['resource_group_name'] sub_id = session.get_subscription_id() target_sub_name = session.get_function_target_subscription_name() function_suffix = StringUtils.naming_hash(rg_name + target_sub_name) storage_suffix = StringUtils.naming_hash(rg_name + sub_id) storage_account = AzureFunctionMode.extract_properties( provision_options, 'storageAccount', { 'name': self.default_storage_name + storage_suffix, 'location': location, 'resource_group_name': rg_name }) app_insights = AzureFunctionMode.extract_properties( provision_options, 'appInsights', { 'name': service_plan['name'], 'location': location, 'resource_group_name': rg_name }) function_app_name = FunctionAppUtilities.get_function_name(self.policy_name, function_suffix) FunctionAppUtilities.validate_function_name(function_app_name) params = FunctionAppUtilities.FunctionAppInfrastructureParameters( app_insights=app_insights, service_plan=service_plan, storage_account=storage_account, function_app_resource_group_name=service_plan['resource_group_name'], function_app_name=function_app_name) return params
def get_function_app_params(self): session = local_session(self.policy.session_factory) provision_options = self.policy.data['mode'].get('provision-options', {}) # Service plan is parsed first, location might be shared with storage & insights service_plan = AzureFunctionMode.extract_properties( provision_options, 'servicePlan', { 'name': 'cloud-custodian', 'location': 'eastus', 'resource_group_name': 'cloud-custodian', 'sku_tier': 'Dynamic', # consumption plan 'sku_name': 'Y1' }) # Metadata used for automatic naming location = service_plan.get('location', 'eastus') rg_name = service_plan['resource_group_name'] sub_id = session.get_subscription_id() target_sub_id = session.get_function_target_subscription_id() function_suffix = StringUtils.naming_hash(rg_name + target_sub_id) storage_suffix = StringUtils.naming_hash(rg_name + sub_id) storage_account = AzureFunctionMode.extract_properties( provision_options, 'storageAccount', { 'name': self.default_storage_name + storage_suffix, 'location': location, 'resource_group_name': rg_name }) app_insights = AzureFunctionMode.extract_properties( provision_options, 'appInsights', { 'name': service_plan['name'], 'location': location, 'resource_group_name': rg_name }) function_app_name = FunctionAppUtilities.get_function_name(self.policy_name, function_suffix) FunctionAppUtilities.validate_function_name(function_app_name) params = FunctionAppUtilities.FunctionAppInfrastructureParameters( app_insights=app_insights, service_plan=service_plan, storage_account=storage_account, function_app_resource_group_name=service_plan['resource_group_name'], function_app_name=function_app_name) return params
def _get_user_from_event(self, event): principal_role = self.principal_role_jmes_path.search(event) principal_type = self.principal_type_jmes_path.search(event) # The Subscription Admins role does not have a principal type if StringUtils.equal(principal_role, 'Subscription Admin'): return self.service_admin_jmes_path.search(event) # ServicePrincipal type elif StringUtils.equal(principal_type, 'ServicePrincipal'): return self.sp_jmes_path.search(event) # Other types (e.g. User, Office 365 Groups, and Security Groups) elif self.upn_jmes_path.search(event): return self.upn_jmes_path.search(event) else: self.log.error('Principal could not be determined.')
def is_match(self, security_rule): if not StringUtils.equal(self.direction_key, security_rule['properties']['direction']): return False ranges_match = self.is_ranges_match(security_rule) protocol_match = (self.ip_protocol is None) or \ (StringUtils.equal(self.ip_protocol, security_rule['properties']['protocol'])) if self.access is not None: access_match = StringUtils.equal( self.access, security_rule['properties']['access']) return self.match_op([ranges_match, protocol_match, access_match]) else: return self.match_op([ranges_match, protocol_match])
def _process_resource_set(self, resources, event=None): client = self.manager.get_client() result = [] for resource in resources: if 'transparentDataEncryption' not in resource['properties']: server_id = resource[ChildTypeInfo.parent_key] server_name = ResourceIdParser.get_resource_name(server_id) tde = client.transparent_data_encryptions.get( resource['resourceGroup'], server_name, resource['name'], "current") resource['properties']['transparentDataEncryption'] = \ tde.serialize(True).get('properties', {}) required_status = 'Enabled' if self.enabled else 'Disabled' if StringUtils.equal( resource['properties']['transparentDataEncryption'].get('status'), required_status): result.append(resource) return result
def _provision(self, params): rg_unit = ResourceGroupUnit() rg_unit.provision_if_not_exists({'name': params['resource_group_name'], 'location': params['location']}) plan_params = AppServicePlan( app_service_plan_name=params['name'], location=params['location'], sku=SkuDescription( name=params['sku_name'], capacity=1, tier=params['sku_tier']), kind='linux', target_worker_size_id=0, reserved=True) plan = self.client.app_service_plans.begin_create_or_update(params['resource_group_name'], params['name'], plan_params).result() # Deploy default autoscale rule for dedicated plans if required by the policy autoscale_params = copy.deepcopy(params.get('auto_scale', {})) if bool(autoscale_params.get('enabled')) and \ not StringUtils.equal(plan.sku, 'dynamic'): autoscale_params['name'] = 'autoscale' autoscale_params['resource_group_name'] = params['resource_group_name'] autoscale_params['service_plan_id'] = plan.id autoscale_params['location'] = plan.location ac_unit = AutoScaleUnit() ac_unit.provision(autoscale_params) return plan
def resource_api_version(self, resource_id): """ latest non-preview api version for resource """ namespace = ResourceIdParser.get_namespace(resource_id) resource_type = ResourceIdParser.get_resource_type(resource_id) cache_id = namespace + resource_type if cache_id in self._provider_cache: return self._provider_cache[cache_id] resource_client = self.client( 'azure.mgmt.resource.ResourceManagementClient') provider = resource_client.providers.get(namespace) # The api version may be directly provided if not provider.resource_types and resource_client.providers.api_version: return resource_client.providers.api_version rt = next((t for t in provider.resource_types if StringUtils.equal(t.resource_type, resource_type)), None) if rt and rt.api_versions: versions = [ v for v in rt.api_versions if 'preview' not in v.lower() ] api_version = versions[0] if versions else rt.api_versions[0] self._provider_cache[cache_id] = api_version return api_version
def provision(config): log = logging.getLogger('c7n_mailer.azure.deploy') function_name = config.get('function_name', 'mailer') function_properties = config.get('function_properties', {}) # service plan is parse first, because its location might be shared with storage & insights service_plan = AzureFunctionMode.extract_properties( function_properties, 'servicePlan', { 'name': 'cloud-custodian', 'location': 'eastus', 'resource_group_name': 'cloud-custodian', 'sku_tier': 'Dynamic', # consumption plan 'sku_name': 'Y1' }) location = service_plan.get('location', 'eastus') rg_name = service_plan['resource_group_name'] sub_id = local_session(Session).get_subscription_id() suffix = StringUtils.naming_hash(rg_name + sub_id) storage_account = AzureFunctionMode.extract_properties( function_properties, 'storageAccount', { 'name': 'mailerstorage' + suffix, 'location': location, 'resource_group_name': rg_name }) app_insights = AzureFunctionMode.extract_properties( function_properties, 'appInsights', { 'name': service_plan['name'], 'location': location, 'resource_group_name': rg_name }) function_app_name = FunctionAppUtilities.get_function_name( '-'.join([service_plan['name'], function_name]), suffix) FunctionAppUtilities.validate_function_name(function_app_name) params = FunctionAppUtilities.FunctionAppInfrastructureParameters( app_insights=app_insights, service_plan=service_plan, storage_account=storage_account, function_app_resource_group_name=service_plan['resource_group_name'], function_app_name=function_app_name) FunctionAppUtilities.deploy_function_app(params) log.info("Building function package for %s" % function_app_name) package = build_function_package(config, function_name, sub_id) log.info("Function package built, size is %dMB" % (package.pkg.size / (1024 * 1024))) FunctionAppUtilities.publish_functions_package(params, package)
def process(self, network_security_groups): ip_protocol = self.data.get(IP_PROTOCOL, '*') direction = self.data[DIRECTION] prefix = self.data.get(PREFIX, 'c7n-policy-') # 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 = prefix + 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)
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)
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)
def provision(config): log = logging.getLogger('c7n_mailer.azure.deploy') function_name = config.get('function_name', 'mailer') function_properties = config.get('function_properties', {}) # service plan is parse first, because its location might be shared with storage & insights service_plan = AzureFunctionMode.extract_properties(function_properties, 'servicePlan', { 'name': 'cloud-custodian', 'location': 'eastus', 'resource_group_name': 'cloud-custodian', 'sku_tier': 'Dynamic', # consumption plan 'sku_name': 'Y1' }) location = service_plan.get('location', 'eastus') rg_name = service_plan['resource_group_name'] sub_id = local_session(Session).get_subscription_id() suffix = StringUtils.naming_hash(rg_name + sub_id) storage_account = AzureFunctionMode.extract_properties(function_properties, 'storageAccount', {'name': 'mailerstorage' + suffix, 'location': location, 'resource_group_name': rg_name}) app_insights = AzureFunctionMode.extract_properties(function_properties, 'appInsights', {'name': service_plan['name'], 'location': location, 'resource_group_name': rg_name}) function_app_name = FunctionAppUtilities.get_function_name( '-'.join([service_plan['name'], function_name]), suffix) FunctionAppUtilities.validate_function_name(function_app_name) params = FunctionAppUtilities.FunctionAppInfrastructureParameters( app_insights=app_insights, service_plan=service_plan, storage_account=storage_account, function_app_resource_group_name=service_plan['resource_group_name'], function_app_name=function_app_name) FunctionAppUtilities.deploy_function_app(params) log.info("Building function package for %s" % function_app_name) package = build_function_package(config, function_name) log.info("Function package built, size is %dMB" % (package.pkg.size / (1024 * 1024))) FunctionAppUtilities.publish_functions_package(params, package)
def extract_properties(options, name, properties): settings = options.get(name, {}) result = {} # str type implies settings is a resource id if isinstance(settings, six.string_types): result['id'] = settings result['name'] = ResourceIdParser.get_resource_name(settings) result['resource_group_name'] = ResourceIdParser.get_resource_group(settings) else: for key in properties.keys(): result[key] = settings.get(StringUtils.snake_to_camel(key), properties[key]) return result
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 extract_properties(options, name, properties): settings = options.get(name, {}) result = {} # str type implies settings is a resource id if isinstance(settings, six.string_types): result['id'] = settings result['name'] = ResourceIdParser.get_resource_name(settings) result['resource_group_name'] = ResourceIdParser.get_resource_group(settings) else: # get nested keys for key in properties.keys(): value = settings.get(StringUtils.snake_to_camel(key), properties[key]) if isinstance(value, dict): result[key] = \ AzureFunctionMode.extract_properties({'v': value}, 'v', properties[key]) else: result[key] = value return result
def resource_api_version(self, resource_id): """ latest non-preview api version for resource """ namespace = ResourceIdParser.get_namespace(resource_id) resource_type = ResourceIdParser.get_resource_type(resource_id) if resource_type in self._provider_cache: return self._provider_cache[resource_type] resource_client = self.client('azure.mgmt.resource.ResourceManagementClient') provider = resource_client.providers.get(namespace) rt = next((t for t in provider.resource_types if StringUtils.equal(t.resource_type, resource_type.split('/')[-1])), None) if rt and rt.api_versions: versions = [v for v in rt.api_versions if 'preview' not in v.lower()] api_version = versions[0] if versions else rt.api_versions[0] self._provider_cache[resource_type] = api_version return api_version
def resource_api_version(self, resource_id): """ latest non-preview api version for resource """ namespace = ResourceIdParser.get_namespace(resource_id) resource_type = ResourceIdParser.get_resource_type(resource_id) cache_id = namespace + resource_type if cache_id in self._provider_cache: return self._provider_cache[cache_id] resource_client = self.client('azure.mgmt.resource.ResourceManagementClient') provider = resource_client.providers.get(namespace) rt = next((t for t in provider.resource_types if StringUtils.equal(t.resource_type, resource_type)), None) if rt and rt.api_versions: versions = [v for v in rt.api_versions if 'preview' not in v.lower()] api_version = versions[0] if versions else rt.api_versions[0] self._provider_cache[cache_id] = api_version return api_version
def test_snake_to_camel(self): self.assertEqual(StringUtils.snake_to_camel(""), "") self.assertEqual(StringUtils.snake_to_camel("test"), "test") self.assertEqual(StringUtils.snake_to_camel("test_abc"), "testAbc") self.assertEqual(StringUtils.snake_to_camel("test_abc_def"), "testAbcDef")
def test_string_utils_equal(self): # Case insensitive matches self.assertTrue(StringUtils.equal("FOO", "foo")) self.assertTrue(StringUtils.equal("fOo", "FoO")) self.assertTrue(StringUtils.equal("ABCDEFGH", "abcdefgh")) self.assertFalse(StringUtils.equal("Foo", "Bar")) # Case sensitive matches self.assertFalse(StringUtils.equal("Foo", "foo", False)) self.assertTrue(StringUtils.equal("foo", "foo", False)) self.assertTrue(StringUtils.equal("fOo", "fOo", False)) self.assertFalse(StringUtils.equal("Foo", "Bar")) # Strip whitespace matches self.assertTrue(StringUtils.equal(" Foo ", "foo")) self.assertTrue(StringUtils.equal("Foo", " foo ")) self.assertTrue(StringUtils.equal(" Foo ", "Foo", False)) self.assertTrue(StringUtils.equal("Foo", " Foo ", False)) # Returns false for non string types self.assertFalse(StringUtils.equal(1, "foo")) self.assertFalse(StringUtils.equal("foo", 1)) self.assertFalse(StringUtils.equal(True, False))
def is_consumption_plan(function_params): return StringUtils.equal(function_params.service_plan['sku_tier'], 'dynamic')
def provision(config): log = logging.getLogger('c7n_mailer.azure.deploy') function_name = config.get('function_name', 'mailer') schedule = config.get('function_schedule', '0 */10 * * * *') function_properties = config.get('function_properties', {}) # service plan is parse first, because its location might be shared with storage & insights service_plan = AzureFunctionMode.extract_properties( function_properties, 'servicePlan', { 'name': 'cloud-custodian', 'location': 'westus2', 'resource_group_name': 'cloud-custodian', 'sku_name': 'B1', 'sku_tier': 'Basic' }) location = service_plan.get('location', 'westus2') rg_name = service_plan['resource_group_name'] sub_id = local_session(Session).get_subscription_id() suffix = StringUtils.naming_hash(rg_name + sub_id) storage_account = AzureFunctionMode.extract_properties( function_properties, 'storageAccount', { 'name': 'mailerstorage' + suffix, 'location': location, 'resource_group_name': rg_name }) app_insights = AzureFunctionMode.extract_properties( function_properties, 'appInsights', { 'name': service_plan['name'], 'location': location, 'resource_group_name': rg_name }) function_app_name = \ '-'.join([service_plan['name'], function_name, suffix]) \ .replace(' ', '-').lower() params = FunctionAppUtilities.FunctionAppInfrastructureParameters( app_insights=app_insights, service_plan=service_plan, storage_account=storage_account, function_app_resource_group_name=service_plan['resource_group_name'], function_app_name=function_app_name) function_app = FunctionAppUtilities().deploy_dedicated_function_app(params) log.info("Building function package for %s" % function_app_name) # Build package packager = FunctionPackage( function_name, os.path.join(os.path.dirname(__file__), 'function.py')) packager.build(None, entry_point=os.path.join(os.path.dirname(__file__), 'handle.py'), extra_modules={'c7n_mailer', 'ruamel'}) packager.pkg.add_contents(function_name + '/config.json', contents=json.dumps(config)) packager.pkg.add_contents(function_name + '/function.json', contents=packager.get_function_config({ 'mode': { 'type': 'azure-periodic', 'schedule': schedule } })) # Add mail templates template_dir = os.path.abspath( os.path.join(os.path.dirname(__file__), '../..', 'msg-templates')) for t in os.listdir(template_dir): with open(os.path.join(template_dir, t)) as fh: packager.pkg.add_contents('msg-templates/%s' % t, fh.read()) packager.close() if packager.wait_for_status(function_app): packager.publish(function_app) else: log.error("Aborted deployment, ensure Application Service is healthy.")
def provision(config): log = logging.getLogger('c7n_mailer.azure.deploy') function_name = config.get('function_name', 'mailer') function_properties = config.get('function_properties', {}) # service plan is parse first, because its location might be shared with storage & insights service_plan = AzureFunctionMode.extract_properties( function_properties, 'servicePlan', { 'name': 'cloud-custodian', 'location': 'westus2', 'resource_group_name': 'cloud-custodian', 'sku_name': 'B1', 'sku_tier': 'Basic' }) location = service_plan.get('location', 'westus2') rg_name = service_plan['resource_group_name'] sub_id = local_session(Session).get_subscription_id() suffix = StringUtils.naming_hash(rg_name + sub_id) storage_account = AzureFunctionMode.extract_properties( function_properties, 'storageAccount', { 'name': 'mailerstorage' + suffix, 'location': location, 'resource_group_name': rg_name }) app_insights = AzureFunctionMode.extract_properties( function_properties, 'appInsights', { 'name': service_plan['name'], 'location': location, 'resource_group_name': rg_name }) function_app_name = \ '-'.join([service_plan['name'], function_name, suffix]) \ .replace(' ', '-').lower() params = FunctionAppUtilities.FunctionAppInfrastructureParameters( app_insights=app_insights, service_plan=service_plan, storage_account=storage_account, function_app_resource_group_name=service_plan['resource_group_name'], function_app_name=function_app_name) FunctionAppUtilities().deploy_dedicated_function_app(params) log.info("Building function package for %s" % function_app_name) package = build_function_package(config, function_name) log.info("Function package built, size is %dMB" % (package.pkg.size / (1024 * 1024))) client = local_session(Session).client( 'azure.mgmt.web.WebSiteManagementClient') publish_creds = client.web_apps.list_publishing_credentials( service_plan['resource_group_name'], function_app_name).result() if package.wait_for_status(publish_creds): package.publish(publish_creds) else: log.error("Aborted deployment, ensure Application Service is healthy.")
def process_resource(self, resource, event_item=None): # if the auto-tag-user policy set update to False (or it's unset) then we # will skip writing their UserName tag and not overwrite pre-existing values if not self.should_update and resource.get('tags', {}).get( self.tag_key, None): return user = self.default_user if event_item: principal_role = self.principal_role_jmes_path.search(event_item) principal_type = self.principal_type_jmes_path.search(event_item) # The Subscription Admins role does not have a principal type if StringUtils.equal(principal_role, 'Subscription Admin'): user = self.service_admin_jmes_path.search(event_item) or user # ServicePrincipal type elif StringUtils.equal(principal_type, 'ServicePrincipal'): user = self.sp_jmes_path.search(event_item) or user # Other types (e.g. User, Office 365 Groups, and Security Groups) elif self.upn_jmes_path.search(event_item): user = self.upn_jmes_path.search(event_item) else: self.log.error('Principal could not be determined.') else: # Calculate start time delta_days = self.data.get('days', self.max_query_days) start_time = utcnow() - datetime.timedelta(days=delta_days) # resource group type if self.manager.type == 'resourcegroup': resource_type = "Microsoft.Resources/subscriptions/resourcegroups" query_filter = " and ".join([ "eventTimestamp ge '%s'" % start_time, "resourceGroupName eq '%s'" % resource['name'], "eventChannels eq 'Operation'" ]) # other Azure resources else: resource_type = resource['type'] query_filter = " and ".join([ "eventTimestamp ge '%s'" % start_time, "resourceUri eq '%s'" % resource['id'], "eventChannels eq 'Operation'" ]) # fetch activity logs logs = self.client.activity_logs.list(filter=query_filter, select=self.query_select) # get the user who issued the first operation operation_name = "%s/write" % resource_type first_op = self.get_first_operation(logs, operation_name) if first_op is not None: user = first_op.caller # issue tag action to label user try: TagHelper.add_tags(self, resource, {self.tag_key: user}) except CloudError as e: # resources can be locked if e.inner_exception.error == 'ScopeLocked': pass
def provision(config): log = logging.getLogger('c7n_mailer.azure.deploy') function_name = config.get('function_name', 'mailer') schedule = config.get('function_schedule', '0 */10 * * * *') function_properties = config.get('function_properties', {}) # service plan is parse first, because its location might be shared with storage & insights service_plan = AzureFunctionMode.extract_properties(function_properties, 'servicePlan', {'name': 'cloud-custodian', 'location': 'westus2', 'resource_group_name': 'cloud-custodian', 'sku_name': 'B1', 'sku_tier': 'Basic'}) location = service_plan.get('location', 'westus2') rg_name = service_plan['resource_group_name'] sub_id = local_session(Session).get_subscription_id() suffix = StringUtils.naming_hash(rg_name + sub_id) storage_account = AzureFunctionMode.extract_properties(function_properties, 'storageAccount', {'name': 'mailerstorage' + suffix, 'location': location, 'resource_group_name': rg_name}) app_insights = AzureFunctionMode.extract_properties(function_properties, 'appInsights', {'name': service_plan['name'], 'location': location, 'resource_group_name': rg_name}) function_app_name = \ '-'.join([service_plan['name'], function_name, suffix]) \ .replace(' ', '-').lower() params = FunctionAppUtilities.FunctionAppInfrastructureParameters( app_insights=app_insights, service_plan=service_plan, storage_account=storage_account, function_app_resource_group_name=service_plan['resource_group_name'], function_app_name=function_app_name) function_app = FunctionAppUtilities().deploy_dedicated_function_app(params) log.info("Building function package for %s" % function_app_name) # Build package packager = FunctionPackage( function_name, os.path.join(os.path.dirname(__file__), 'function.py')) packager.build(None, entry_point=os.path.join(os.path.dirname(__file__), 'handle.py'), extra_modules={'c7n_mailer', 'ruamel'}) packager.pkg.add_contents( function_name + '/config.json', contents=json.dumps(config)) packager.pkg.add_contents( function_name + '/function.json', contents=packager.get_function_config({'mode': {'type': 'azure-periodic', 'schedule': schedule}})) # Add mail templates template_dir = os.path.abspath( os.path.join(os.path.dirname(__file__), '../..', 'msg-templates')) for t in os.listdir(template_dir): with open(os.path.join(template_dir, t)) as fh: packager.pkg.add_contents('msg-templates/%s' % t, fh.read()) packager.close() if packager.wait_for_status(function_app): packager.publish(function_app) else: log.error("Aborted deployment, ensure Application Service is healthy.")