Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/c7n/resources/awslambda.py: 37%
462 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-08 06:51 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-08 06:51 +0000
1# Copyright The Cloud Custodian Authors.
2# SPDX-License-Identifier: Apache-2.0
3import json
4from urllib.parse import urlparse, parse_qs
6from botocore.exceptions import ClientError
7from botocore.paginate import Paginator
8from concurrent.futures import as_completed
9from datetime import timedelta, datetime
11from c7n.actions import Action, RemovePolicyBase, ModifyVpcSecurityGroupsAction
12from c7n.filters import CrossAccountAccessFilter, ValueFilter, Filter
13from c7n.filters.kms import KmsRelatedFilter
14import c7n.filters.vpc as net_filters
15from c7n.manager import resources
16from c7n import query, utils
17from c7n.resources.iam import CheckPermissions, SpecificIamRoleManagedPolicy
18from c7n.tags import universal_augment
19from c7n.utils import (
20 local_session,
21 type_schema,
22 select_keys,
23 get_human_size,
24 parse_date,
25 get_retry,
26 jmespath_search,
27 jmespath_compile
28)
29from botocore.config import Config
30from .securityhub import PostFinding
32ErrAccessDenied = "AccessDeniedException"
35class DescribeLambda(query.DescribeSource):
37 def augment(self, resources):
38 return universal_augment(
39 self.manager, super(DescribeLambda, self).augment(resources))
41 def get_resources(self, ids):
42 client = local_session(self.manager.session_factory).client('lambda')
43 resources = []
44 for rid in ids:
45 try:
46 func = self.manager.retry(client.get_function, FunctionName=rid)
47 except client.exceptions.ResourceNotFoundException:
48 continue
49 config = func.pop('Configuration')
50 config.update(func)
51 if 'Tags' in config:
52 config['Tags'] = [
53 {'Key': k, 'Value': v} for k, v in config['Tags'].items()]
54 resources.append(config)
55 return resources
58class ConfigLambda(query.ConfigSource):
60 def load_resource(self, item):
61 resource = super(ConfigLambda, self).load_resource(item)
62 resource['c7n:Policy'] = item[
63 'supplementaryConfiguration'].get('Policy')
64 return resource
67@resources.register('lambda')
68class AWSLambda(query.QueryResourceManager):
70 class resource_type(query.TypeInfo):
71 service = 'lambda'
72 arn = 'FunctionArn'
73 arn_type = 'function'
74 arn_separator = ":"
75 enum_spec = ('list_functions', 'Functions', None)
76 name = id = 'FunctionName'
77 date = 'LastModified'
78 dimension = 'FunctionName'
79 config_type = 'AWS::Lambda::Function'
80 cfn_type = 'AWS::Lambda::Function'
81 universal_taggable = object()
83 source_mapping = {
84 'describe': DescribeLambda,
85 'config': ConfigLambda
86 }
88 def get_resources(self, ids, cache=True, augment=False):
89 return super(AWSLambda, self).get_resources(ids, cache, augment)
92@AWSLambda.filter_registry.register('security-group')
93class SecurityGroupFilter(net_filters.SecurityGroupFilter):
95 RelatedIdsExpression = "VpcConfig.SecurityGroupIds[]"
98@AWSLambda.filter_registry.register('subnet')
99class SubnetFilter(net_filters.SubnetFilter):
101 RelatedIdsExpression = "VpcConfig.SubnetIds[]"
104@AWSLambda.filter_registry.register('vpc')
105class VpcFilter(net_filters.VpcFilter):
107 RelatedIdsExpression = "VpcConfig.VpcId"
110AWSLambda.filter_registry.register('network-location', net_filters.NetworkLocation)
113@AWSLambda.filter_registry.register('check-permissions')
114class LambdaPermissions(CheckPermissions):
116 def get_iam_arns(self, resources):
117 return [r['Role'] for r in resources]
120@AWSLambda.filter_registry.register('reserved-concurrency')
121class ReservedConcurrency(ValueFilter):
123 annotation_key = "c7n:FunctionInfo"
124 value_key = '"c7n:FunctionInfo".Concurrency.ReservedConcurrentExecutions'
125 schema = type_schema('reserved-concurrency', rinherit=ValueFilter.schema)
126 schema_alias = False
127 permissions = ('lambda:GetFunction',)
129 def validate(self):
130 self.data['key'] = self.value_key
131 return super(ReservedConcurrency, self).validate()
133 def process(self, resources, event=None):
134 self.data['key'] = self.value_key
135 client = local_session(self.manager.session_factory).client('lambda')
137 def _augment(r):
138 try:
139 r[self.annotation_key] = self.manager.retry(
140 client.get_function, FunctionName=r['FunctionArn'])
141 r[self.annotation_key].pop('ResponseMetadata')
142 except ClientError as e:
143 if e.response['Error']['Code'] == ErrAccessDenied:
144 self.log.warning(
145 "Access denied getting lambda:%s",
146 r['FunctionName'])
147 raise
148 return r
150 with self.executor_factory(max_workers=3) as w:
151 resources = list(filter(None, w.map(_augment, resources)))
152 return super(ReservedConcurrency, self).process(resources, event)
155def get_lambda_policies(client, executor_factory, resources, log):
157 def _augment(r):
158 try:
159 r['c7n:Policy'] = client.get_policy(
160 FunctionName=r['FunctionName'])['Policy']
161 except client.exceptions.ResourceNotFoundException:
162 return None
163 except ClientError as e:
164 if e.response['Error']['Code'] == 'AccessDeniedException':
165 log.warning(
166 "Access denied getting policy lambda:%s",
167 r['FunctionName'])
168 return r
170 results = []
171 futures = {}
173 with executor_factory(max_workers=3) as w:
174 for r in resources:
175 if 'c7n:Policy' in r:
176 results.append(r)
177 continue
178 futures[w.submit(_augment, r)] = r
180 for f in as_completed(futures):
181 if f.exception():
182 log.warning("Error getting policy for:%s err:%s",
183 r['FunctionName'], f.exception())
184 r = futures[f]
185 continue
186 results.append(f.result())
188 return filter(None, results)
191@AWSLambda.filter_registry.register('event-source')
192class LambdaEventSource(ValueFilter):
193 # this uses iam policy, it should probably use
194 # event source mapping api
196 annotation_key = "c7n:EventSources"
197 schema = type_schema('event-source', rinherit=ValueFilter.schema)
198 schema_alias = False
199 permissions = ('lambda:GetPolicy',)
201 def process(self, resources, event=None):
202 client = local_session(self.manager.session_factory).client('lambda')
203 self.log.debug("fetching policy for %d lambdas" % len(resources))
204 resources = get_lambda_policies(
205 client, self.executor_factory, resources, self.log)
206 self.data['key'] = self.annotation_key
207 return super(LambdaEventSource, self).process(resources, event)
209 def __call__(self, r):
210 if 'c7n:Policy' not in r:
211 return False
212 sources = set()
213 data = json.loads(r['c7n:Policy'])
214 for s in data.get('Statement', ()):
215 if s['Effect'] != 'Allow':
216 continue
217 if 'Service' in s['Principal']:
218 sources.add(s['Principal']['Service'])
219 if sources:
220 r[self.annotation_key] = list(sources)
221 return self.match(r)
224@AWSLambda.filter_registry.register('cross-account')
225class LambdaCrossAccountAccessFilter(CrossAccountAccessFilter):
226 """Filters lambda functions with cross-account permissions
228 The whitelist parameter can be used to prevent certain accounts
229 from being included in the results (essentially stating that these
230 accounts permissions are allowed to exist)
232 This can be useful when combining this filter with the delete action.
234 :example:
236 .. code-block:: yaml
238 policies:
239 - name: lambda-cross-account
240 resource: lambda
241 filters:
242 - type: cross-account
243 whitelist:
244 - 'IAM-Policy-Cross-Account-Access'
246 """
247 permissions = ('lambda:GetPolicy',)
249 policy_attribute = 'c7n:Policy'
251 def process(self, resources, event=None):
252 client = local_session(self.manager.session_factory).client('lambda')
253 self.log.debug("fetching policy for %d lambdas" % len(resources))
254 resources = get_lambda_policies(
255 client, self.executor_factory, resources, self.log)
256 return super(LambdaCrossAccountAccessFilter, self).process(
257 resources, event)
260@AWSLambda.filter_registry.register('kms-key')
261class KmsFilter(KmsRelatedFilter):
263 RelatedIdsExpression = 'KMSKeyArn'
266@AWSLambda.filter_registry.register('has-specific-managed-policy')
267class HasSpecificManagedPolicy(SpecificIamRoleManagedPolicy):
268 """Filter an lambda function that has an IAM execution role that has a
269 specific managed IAM policy.
271 :example:
273 .. code-block:: yaml
275 policies:
276 - name: lambda-has-admin-policy
277 resource: aws.lambda
278 filters:
279 - type: has-specific-managed-policy
280 value: admin-policy
282 """
284 permissions = ('iam:ListAttachedRolePolicies',)
286 def process(self, resources, event=None):
287 client = utils.local_session(self.manager.session_factory).client('iam')
289 results = []
290 roles = {
291 r['Role']: {
292 'RoleName': r['Role'].split('/')[-1]
293 }
294 for r in resources
295 }
297 for role in roles.values():
298 self.get_managed_policies(client, [role])
299 for r in resources:
300 role_arn = r['Role']
301 matched_keys = [k for k in roles[role_arn][self.annotation_key] if self.match(k)]
302 self.merge_annotation(role, self.matched_annotation_key, matched_keys)
303 if matched_keys:
304 results.append(r)
306 return results
308@AWSLambda.action_registry.register('set-xray-tracing')
309class LambdaEnableXrayTracing(Action):
310 """
311 This action allows for enable Xray tracing to Active
313 :example:
315 .. code-block:: yaml
317 actions:
318 - type: enable-xray-tracing
319 """
321 schema = type_schema(
322 'set-xray-tracing',
323 **{'state': {'default': True, 'type': 'boolean'}}
324 )
325 permissions = ("lambda:UpdateFunctionConfiguration",)
327 def get_mode_val(self, state):
328 if state:
329 return "Active"
330 return "PassThrough"
332 def process(self, resources):
333 """
334 Enables the Xray Tracing for the function.
336 Args:
337 resources: AWS lamdba resources
338 Returns:
339 None
340 """
341 config = Config(
342 retries={
343 'max_attempts': 8,
344 'mode': 'standard'
345 }
346 )
347 client = local_session(self.manager.session_factory).client('lambda', config=config)
348 updateState = self.data.get('state', True)
349 retry = get_retry(('TooManyRequestsException', 'ResourceConflictException'))
351 mode = self.get_mode_val(updateState)
352 for resource in resources:
353 state = bool(resource["TracingConfig"]["Mode"] == "Active")
354 if updateState != state:
355 function_name = resource["FunctionName"]
356 self.log.info(f"Set Xray tracing to {mode} for lambda {function_name}")
357 try:
358 retry(
359 client.update_function_configuration,
360 FunctionName=function_name,
361 TracingConfig={
362 'Mode': mode
363 }
364 )
365 except client.exceptions.ResourceNotFoundException:
366 continue
369@AWSLambda.action_registry.register('post-finding')
370class LambdaPostFinding(PostFinding):
372 resource_type = 'AwsLambdaFunction'
374 def format_resource(self, r):
375 envelope, payload = self.format_envelope(r)
376 # security hub formatting beggars belief
377 details = self.filter_empty(select_keys(r,
378 ['CodeSha256',
379 'DeadLetterConfig',
380 'Environment',
381 'Handler',
382 'LastModified',
383 'MemorySize',
384 'MasterArn',
385 'RevisionId',
386 'Role',
387 'Runtime',
388 'TracingConfig',
389 'Timeout',
390 'Version',
391 'VpcConfig']))
392 # check and set the correct formatting value for kms key arn if it exists
393 kms_value = r.get('KMSKeyArn')
394 if kms_value is not None:
395 details['KmsKeyArn'] = kms_value
396 # do the brain dead parts Layers, Code, TracingConfig
397 if 'Layers' in r:
398 r['Layers'] = {
399 'Arn': r['Layers'][0]['Arn'],
400 'CodeSize': r['Layers'][0]['CodeSize']}
401 details.get('VpcConfig', {}).pop('VpcId', None)
403 if 'Code' in r and r['Code'].get('RepositoryType') == "S3":
404 parsed = urlparse(r['Code']['Location'])
405 details['Code'] = {
406 'S3Bucket': parsed.netloc.split('.', 1)[0],
407 'S3Key': parsed.path[1:]}
408 params = parse_qs(parsed.query)
409 if params['versionId']:
410 details['Code']['S3ObjectVersion'] = params['versionId'][0]
411 payload.update(details)
412 return envelope
415@AWSLambda.action_registry.register('trim-versions')
416class VersionTrim(Action):
417 """Delete old versions of a function.
419 By default this will only remove the non $LATEST
420 version of a function that are not referenced by
421 an alias. Optionally it can delete only versions
422 older than a given age.
424 :example:
426 .. code-block:: yaml
428 policies:
429 - name: lambda-gc
430 resource: aws.lambda
431 actions:
432 - type: trim-versions
433 exclude-aliases: true # default true
434 older-than: 60 # default not-set
435 retain-latest: true # default false
437 retain-latest refers to whether the latest numeric
438 version will be retained, the $LATEST alias will
439 still point to the last revision even without this set,
440 so this is safe wrt to the function availability, its more
441 about desire to retain an explicit version of the current
442 code, rather than just the $LATEST alias pointer which will
443 be automatically updated.
444 """
445 permissions = ('lambda:ListAliases', 'lambda:ListVersionsByFunction',
446 'lambda:DeleteFunction',)
448 schema = type_schema(
449 'trim-versions',
450 **{'exclude-aliases': {'default': True, 'type': 'boolean'},
451 'retain-latest': {'default': True, 'type': 'boolean'},
452 'older-than': {'type': 'number'}})
454 def process(self, resources):
455 client = local_session(self.manager.session_factory).client('lambda')
456 matched = total = 0
457 for r in resources:
458 fmatched, ftotal = self.process_lambda(client, r)
459 matched += fmatched
460 total += ftotal
461 self.log.info('trim-versions cleaned %s of %s lambda storage' % (
462 get_human_size(matched), get_human_size(total)))
464 def get_aliased_versions(self, client, r):
465 aliases_pager = client.get_paginator('list_aliases')
466 aliases_pager.PAGE_ITERATOR_CLASS = query.RetryPageIterator
467 aliases = aliases_pager.paginate(
468 FunctionName=r['FunctionName']).build_full_result().get('Aliases')
470 aliased_versions = set()
471 for a in aliases:
472 aliased_versions.add("%s:%s" % (
473 a['AliasArn'].rsplit(':', 1)[0], a['FunctionVersion']))
474 return aliased_versions
476 def process_lambda(self, client, r):
477 exclude_aliases = self.data.get('exclude-aliases', True)
478 retain_latest = self.data.get('retain-latest', False)
479 date_threshold = self.data.get('older-than')
480 date_threshold = (
481 date_threshold and
482 parse_date(datetime.utcnow()) - timedelta(days=date_threshold) or
483 None)
484 aliased_versions = ()
486 if exclude_aliases:
487 aliased_versions = self.get_aliased_versions(client, r)
489 versions_pager = client.get_paginator('list_versions_by_function')
490 versions_pager.PAGE_ITERATOR_CLASS = query.RetryPageIterator
491 pager = versions_pager.paginate(FunctionName=r['FunctionName'])
493 matched = total = 0
494 latest_sha = None
496 for page in pager:
497 versions = page.get('Versions')
498 for v in versions:
499 if v['Version'] == '$LATEST':
500 latest_sha = v['CodeSha256']
501 continue
502 total += v['CodeSize']
503 if v['FunctionArn'] in aliased_versions:
504 continue
505 if date_threshold and parse_date(v['LastModified']) > date_threshold:
506 continue
507 # Retain numbered version, not required, but it feels like a good thing
508 # to do. else the latest alias will still point.
509 if retain_latest and latest_sha and v['CodeSha256'] == latest_sha:
510 continue
511 matched += v['CodeSize']
512 self.manager.retry(
513 client.delete_function, FunctionName=v['FunctionArn'])
514 return (matched, total)
517@AWSLambda.action_registry.register('remove-statements')
518class RemovePolicyStatement(RemovePolicyBase):
519 """Action to remove policy/permission statements from lambda functions.
521 :example:
523 .. code-block:: yaml
525 policies:
526 - name: lambda-remove-cross-accounts
527 resource: lambda
528 filters:
529 - type: cross-account
530 actions:
531 - type: remove-statements
532 statement_ids: matched
533 """
535 schema = type_schema(
536 'remove-statements',
537 required=['statement_ids'],
538 statement_ids={'oneOf': [
539 {'enum': ['matched']},
540 {'type': 'array', 'items': {'type': 'string'}}]})
542 permissions = ("lambda:GetPolicy", "lambda:RemovePermission")
544 def process(self, resources):
545 results = []
546 client = local_session(self.manager.session_factory).client('lambda')
547 for r in resources:
548 try:
549 if self.process_resource(client, r):
550 results.append(r)
551 except Exception:
552 self.log.exception(
553 "Error processing lambda %s", r['FunctionArn'])
554 return results
556 def process_resource(self, client, resource):
557 if 'c7n:Policy' not in resource:
558 try:
559 resource['c7n:Policy'] = client.get_policy(
560 FunctionName=resource['FunctionName']).get('Policy')
561 except ClientError as e:
562 if e.response['Error']['Code'] != ErrAccessDenied:
563 raise
564 resource['c7n:Policy'] = None
566 if not resource['c7n:Policy']:
567 return
569 p = json.loads(resource['c7n:Policy'])
571 statements, found = self.process_policy(
572 p, resource, CrossAccountAccessFilter.annotation_key)
573 if not found:
574 return
576 for f in found:
577 client.remove_permission(
578 FunctionName=resource['FunctionName'],
579 StatementId=f['Sid'])
582@AWSLambda.action_registry.register('set-concurrency')
583class SetConcurrency(Action):
584 """Set lambda function concurrency to the desired level.
586 Can be used to set the reserved function concurrency to an exact value,
587 to delete reserved concurrency, or to set the value to an attribute of
588 the resource.
589 """
591 schema = type_schema(
592 'set-concurrency',
593 required=('value',),
594 **{'expr': {'type': 'boolean'},
595 'value': {'oneOf': [
596 {'type': 'string'},
597 {'type': 'integer'},
598 {'type': 'null'}]}})
600 permissions = ('lambda:DeleteFunctionConcurrency',
601 'lambda:PutFunctionConcurrency')
603 def validate(self):
604 if self.data.get('expr', False) and not isinstance(self.data['value'], str):
605 raise ValueError("invalid value expression %s" % self.data['value'])
606 return self
608 def process(self, functions):
609 client = local_session(self.manager.session_factory).client('lambda')
610 is_expr = self.data.get('expr', False)
611 value = self.data['value']
612 if is_expr:
613 value = jmespath_compile(value)
615 none_type = type(None)
617 for function in functions:
618 fvalue = value
619 if is_expr:
620 fvalue = value.search(function)
621 if isinstance(fvalue, float):
622 fvalue = int(fvalue)
623 if isinstance(value, int) or isinstance(value, none_type):
624 self.policy.log.warning(
625 "Function: %s Invalid expression value for concurrency: %s",
626 function['FunctionName'], fvalue)
627 continue
628 if fvalue is None:
629 client.delete_function_concurrency(
630 FunctionName=function['FunctionName'])
631 else:
632 client.put_function_concurrency(
633 FunctionName=function['FunctionName'],
634 ReservedConcurrentExecutions=fvalue)
637@AWSLambda.action_registry.register('delete')
638class Delete(Action):
639 """Delete a lambda function (including aliases and older versions).
641 :example:
643 .. code-block:: yaml
645 policies:
646 - name: lambda-delete-dotnet-functions
647 resource: lambda
648 filters:
649 - Runtime: dotnetcore1.0
650 actions:
651 - delete
652 """
653 schema = type_schema('delete')
654 permissions = ("lambda:DeleteFunction",)
656 def process(self, functions):
657 client = local_session(self.manager.session_factory).client('lambda')
658 for function in functions:
659 try:
660 client.delete_function(FunctionName=function['FunctionName'])
661 except ClientError as e:
662 if e.response['Error']['Code'] == "ResourceNotFoundException":
663 continue
664 raise
665 self.log.debug("Deleted %d functions", len(functions))
668@AWSLambda.action_registry.register('modify-security-groups')
669class LambdaModifyVpcSecurityGroups(ModifyVpcSecurityGroupsAction):
671 permissions = ("lambda:UpdateFunctionConfiguration",)
673 def process(self, functions):
674 client = local_session(self.manager.session_factory).client('lambda')
675 groups = super(LambdaModifyVpcSecurityGroups, self).get_groups(
676 functions)
678 for idx, i in enumerate(functions):
679 if 'VpcConfig' not in i: # only continue if Lambda func is VPC-enabled
680 continue
681 try:
682 client.update_function_configuration(FunctionName=i['FunctionName'],
683 VpcConfig={'SecurityGroupIds': groups[idx]})
684 except client.exceptions.ResourceNotFoundException:
685 continue
688@resources.register('lambda-layer')
689class LambdaLayerVersion(query.QueryResourceManager):
690 """Note custodian models the lambda layer version.
692 Layers end up being a logical asset, the physical asset for use
693 and management is the layer verison.
695 To ease that distinction, we support querying just the latest
696 layer version or having a policy against all layer versions.
698 By default we query all versions, the following is an example
699 to query just the latest.
701 .. code-block:: yaml
703 policies:
704 - name: lambda-layer
705 resource: lambda
706 query:
707 - version: latest
709 """
711 class resource_type(query.TypeInfo):
712 service = 'lambda'
713 enum_spec = ('list_layers', 'Layers', None)
714 name = id = 'LayerName'
715 date = 'CreatedDate'
716 arn = "LayerVersionArn"
717 arn_type = "layer"
718 cfn_type = 'AWS::Lambda::LayerVersion'
720 def augment(self, resources):
721 versions = {}
722 for r in resources:
723 versions[r['LayerName']] = v = r['LatestMatchingVersion']
724 v['LayerName'] = r['LayerName']
726 if {'version': 'latest'} in self.data.get('query', []):
727 return list(versions.values())
729 layer_names = list(versions)
730 client = local_session(self.session_factory).client('lambda')
732 versions = []
733 for layer_name in layer_names:
734 pager = get_layer_version_paginator(client)
735 for v in pager.paginate(
736 LayerName=layer_name).build_full_result().get('LayerVersions'):
737 v['LayerName'] = layer_name
738 versions.append(v)
739 return versions
742def get_layer_version_paginator(client):
743 pager = Paginator(
744 client.list_layer_versions,
745 {'input_token': 'NextToken',
746 'output_token': 'NextToken',
747 'result_key': 'LayerVersions'},
748 client.meta.service_model.operation_model('ListLayerVersions'))
749 pager.PAGE_ITERATOR_CLS = query.RetryPageIterator
750 return pager
753@LambdaLayerVersion.filter_registry.register('cross-account')
754class LayerCrossAccount(CrossAccountAccessFilter):
756 permissions = ('lambda:GetLayerVersionPolicy',)
758 def process(self, resources, event=None):
759 client = local_session(self.manager.session_factory).client('lambda')
760 for r in resources:
761 if 'c7n:Policy' in r:
762 continue
763 try:
764 rpolicy = self.manager.retry(
765 client.get_layer_version_policy,
766 LayerName=r['LayerName'],
767 VersionNumber=r['Version']).get('Policy')
768 except client.exceptions.ResourceNotFoundException:
769 rpolicy = {}
770 r['c7n:Policy'] = rpolicy
771 return super(LayerCrossAccount, self).process(resources)
773 def get_resource_policy(self, r):
774 return r['c7n:Policy']
777@LambdaLayerVersion.action_registry.register('remove-statements')
778class LayerRemovePermissions(RemovePolicyBase):
780 schema = type_schema(
781 'remove-statements',
782 required=['statement_ids'],
783 statement_ids={'oneOf': [
784 {'enum': ['matched']},
785 {'type': 'array', 'items': {'type': 'string'}}]})
787 permissions = (
788 "lambda:GetLayerVersionPolicy",
789 "lambda:RemoveLayerVersionPermission")
791 def process(self, resources):
792 client = local_session(self.manager.session_factory).client('lambda')
793 for r in resources:
794 self.process_resource(client, r)
796 def process_resource(self, client, r):
797 if 'c7n:Policy' not in r:
798 try:
799 r['c7n:Policy'] = self.manager.retry(
800 client.get_layer_version_policy,
801 LayerName=r['LayerName'],
802 VersionNumber=r['Version'])
803 except client.exceptions.ResourceNotFound:
804 return
806 p = json.loads(r['c7n:Policy'])
808 statements, found = self.process_policy(
809 p, r, CrossAccountAccessFilter.annotation_key)
811 if not found:
812 return
814 for f in found:
815 self.manager.retry(
816 client.remove_layer_version_permission,
817 LayerName=r['LayerName'],
818 StatementId=f['Sid'],
819 VersionNumber=r['Version'])
822@LambdaLayerVersion.action_registry.register('delete')
823class DeleteLayerVersion(Action):
825 schema = type_schema('delete')
826 permissions = ('lambda:DeleteLayerVersion',)
828 def process(self, resources):
829 client = local_session(
830 self.manager.session_factory).client('lambda')
832 for r in resources:
833 try:
834 self.manager.retry(
835 client.delete_layer_version,
836 LayerName=r['LayerName'],
837 VersionNumber=r['Version'])
838 except client.exceptions.ResourceNotFound:
839 continue
842@LambdaLayerVersion.action_registry.register('post-finding')
843class LayerPostFinding(PostFinding):
845 resource_type = 'AwsLambdaLayerVersion'
847 def format_resource(self, r):
848 envelope, payload = self.format_envelope(r)
849 payload.update(self.filter_empty(
850 select_keys(r, ['Version', 'CreatedDate', 'CompatibleRuntimes'])))
851 return envelope
854@AWSLambda.filter_registry.register('lambda-edge')
856class LambdaEdgeFilter(Filter):
857 """
858 Filter for lambda@edge functions. Lambda@edge only exists in us-east-1
860 :example:
862 .. code-block:: yaml
864 policies:
865 - name: lambda-edge-filter
866 resource: lambda
867 region: us-east-1
868 filters:
869 - type: lambda-edge
870 state: True
871 """
872 permissions = ('cloudfront:ListDistributions',)
874 schema = type_schema('lambda-edge',
875 **{'state': {'type': 'boolean'}})
877 def get_lambda_cf_map(self):
878 cfs = self.manager.get_resource_manager('distribution').resources()
879 func_expressions = ('DefaultCacheBehavior.LambdaFunctionAssociations.Items',
880 'CacheBehaviors.LambdaFunctionAssociations.Items')
881 lambda_dist_map = {}
882 for d in cfs:
883 for exp in func_expressions:
884 if jmespath_search(exp, d):
885 for function in jmespath_search(exp, d):
886 # Geting rid of the version number in the arn
887 lambda_edge_arn = ':'.join(function['LambdaFunctionARN'].split(':')[:-1])
888 lambda_dist_map.setdefault(lambda_edge_arn, []).append(d['Id'])
889 return lambda_dist_map
891 def process(self, resources, event=None):
892 results = []
893 if self.manager.config.region != 'us-east-1' and self.data.get('state'):
894 return []
895 annotation_key = 'c7n:DistributionIds'
896 lambda_edge_cf_map = self.get_lambda_cf_map()
897 for r in resources:
898 if (r['FunctionArn'] in lambda_edge_cf_map and self.data.get('state')):
899 r[annotation_key] = lambda_edge_cf_map.get(r['FunctionArn'])
900 results.append(r)
901 elif (r['FunctionArn'] not in lambda_edge_cf_map and not self.data.get('state')):
902 results.append(r)
903 return results