Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/c7n/resources/awslambda.py: 35%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

511 statements  

1# Copyright The Cloud Custodian Authors. 

2# SPDX-License-Identifier: Apache-2.0 

3import json 

4from urllib.parse import urlparse, parse_qs 

5 

6from botocore.exceptions import ClientError 

7from botocore.paginate import Paginator 

8from concurrent.futures import as_completed 

9from datetime import timedelta, datetime 

10 

11from c7n.actions import Action, RemovePolicyBase, ModifyVpcSecurityGroupsAction 

12from c7n.filters import CrossAccountAccessFilter, ValueFilter, Filter 

13from c7n.filters.costhub import CostHubRecommendation 

14from c7n.filters.kms import KmsRelatedFilter 

15import c7n.filters.vpc as net_filters 

16from c7n.manager import resources 

17from c7n import query, utils 

18from c7n.resources.aws import shape_validate 

19from c7n.resources.iam import CheckPermissions, SpecificIamRoleManagedPolicy 

20from c7n.tags import universal_augment 

21from c7n.utils import ( 

22 local_session, 

23 type_schema, 

24 select_keys, 

25 get_human_size, 

26 parse_date, 

27 get_retry, 

28 jmespath_search, 

29 jmespath_compile 

30) 

31from botocore.config import Config 

32from .securityhub import PostFinding 

33 

34ErrAccessDenied = "AccessDeniedException" 

35 

36 

37class DescribeLambda(query.DescribeSource): 

38 

39 def augment(self, resources): 

40 return universal_augment( 

41 self.manager, super(DescribeLambda, self).augment(resources)) 

42 

43 def get_resources(self, ids): 

44 client = local_session(self.manager.session_factory).client('lambda') 

45 resources = [] 

46 for rid in ids: 

47 try: 

48 func = self.manager.retry(client.get_function, FunctionName=rid) 

49 except client.exceptions.ResourceNotFoundException: 

50 continue 

51 config = func.pop('Configuration') 

52 config.update(func) 

53 if 'Tags' in config: 

54 config['Tags'] = [ 

55 {'Key': k, 'Value': v} for k, v in config['Tags'].items()] 

56 resources.append(config) 

57 return resources 

58 

59 

60class ConfigLambda(query.ConfigSource): 

61 

62 def load_resource(self, item): 

63 resource = super(ConfigLambda, self).load_resource(item) 

64 resource['c7n:Policy'] = item[ 

65 'supplementaryConfiguration'].get('Policy') 

66 return resource 

67 

68 

69@resources.register('lambda') 

70class AWSLambda(query.QueryResourceManager): 

71 

72 class resource_type(query.TypeInfo): 

73 service = 'lambda' 

74 arn = 'FunctionArn' 

75 arn_type = 'function' 

76 arn_separator = ":" 

77 enum_spec = ('list_functions', 'Functions', None) 

78 name = id = 'FunctionName' 

79 date = 'LastModified' 

80 dimension = 'FunctionName' 

81 config_type = 'AWS::Lambda::Function' 

82 cfn_type = 'AWS::Lambda::Function' 

83 universal_taggable = object() 

84 permissions_augment = ("lambda:ListTags",) 

85 

86 source_mapping = { 

87 'describe': DescribeLambda, 

88 'config': ConfigLambda 

89 } 

90 

91 def get_resources(self, ids, cache=True, augment=False): 

92 return super(AWSLambda, self).get_resources(ids, cache, augment) 

93 

94 

95@AWSLambda.filter_registry.register('security-group') 

96class SecurityGroupFilter(net_filters.SecurityGroupFilter): 

97 

98 RelatedIdsExpression = "VpcConfig.SecurityGroupIds[]" 

99 

100 

101@AWSLambda.filter_registry.register('subnet') 

102class SubnetFilter(net_filters.SubnetFilter): 

103 

104 RelatedIdsExpression = "VpcConfig.SubnetIds[]" 

105 

106 

107@AWSLambda.filter_registry.register('vpc') 

108class VpcFilter(net_filters.VpcFilter): 

109 

110 RelatedIdsExpression = "VpcConfig.VpcId" 

111 

112 

113AWSLambda.filter_registry.register('network-location', net_filters.NetworkLocation) 

114 

115 

116@AWSLambda.filter_registry.register('check-permissions') 

117class LambdaPermissions(CheckPermissions): 

118 

119 def get_iam_arns(self, resources): 

120 return [r['Role'] for r in resources] 

121 

122 

123@AWSLambda.filter_registry.register('url-config') 

124class URLConfig(ValueFilter): 

125 

126 annotation_key = "c7n:UrlConfig" 

127 schema = type_schema('url-config', rinherit=ValueFilter.schema) 

128 schema_alias = False 

129 permissions = ('lambda:GetFunctionUrlConfig',) 

130 

131 def process(self, resources, event=None): 

132 client = local_session(self.manager.session_factory).client('lambda') 

133 

134 def _augment(r): 

135 try: 

136 r[self.annotation_key] = self.manager.retry( 

137 client.get_function_url_config, FunctionName=r['FunctionArn']) 

138 r[self.annotation_key].pop('ResponseMetadata') 

139 except client.exceptions.ResourceNotFoundException: 

140 r[self.annotation_key] = {} 

141 return r 

142 

143 with self.executor_factory(max_workers=2) as w: 

144 resources = list(filter(None, w.map(_augment, resources))) 

145 

146 return super().process(resources, event) 

147 

148 def __call__(self, i): 

149 return super().__call__(i[self.annotation_key]) 

150 

151 

152@AWSLambda.filter_registry.register('reserved-concurrency') 

153class ReservedConcurrency(ValueFilter): 

154 

155 annotation_key = "c7n:FunctionInfo" 

156 value_key = '"c7n:FunctionInfo".Concurrency.ReservedConcurrentExecutions' 

157 schema = type_schema('reserved-concurrency', rinherit=ValueFilter.schema) 

158 schema_alias = False 

159 permissions = ('lambda:GetFunction',) 

160 

161 def validate(self): 

162 self.data['key'] = self.value_key 

163 return super(ReservedConcurrency, self).validate() 

164 

165 def process(self, resources, event=None): 

166 self.data['key'] = self.value_key 

167 client = local_session(self.manager.session_factory).client('lambda') 

168 

169 def _augment(r): 

170 try: 

171 r[self.annotation_key] = self.manager.retry( 

172 client.get_function, FunctionName=r['FunctionArn']) 

173 r[self.annotation_key].pop('ResponseMetadata') 

174 except ClientError as e: 

175 if e.response['Error']['Code'] == ErrAccessDenied: 

176 self.log.warning( 

177 "Access denied getting lambda:%s", 

178 r['FunctionName']) 

179 raise 

180 return r 

181 

182 with self.executor_factory(max_workers=3) as w: 

183 resources = list(filter(None, w.map(_augment, resources))) 

184 return super(ReservedConcurrency, self).process(resources, event) 

185 

186 

187def get_lambda_policies(client, executor_factory, resources, log): 

188 

189 def _augment(r): 

190 try: 

191 r['c7n:Policy'] = client.get_policy( 

192 FunctionName=r['FunctionName'])['Policy'] 

193 except client.exceptions.ResourceNotFoundException: 

194 return None 

195 except ClientError as e: 

196 if e.response['Error']['Code'] == 'AccessDeniedException': 

197 log.warning( 

198 "Access denied getting policy lambda:%s", 

199 r['FunctionName']) 

200 return r 

201 

202 results = [] 

203 futures = {} 

204 

205 with executor_factory(max_workers=3) as w: 

206 for r in resources: 

207 if 'c7n:Policy' in r: 

208 results.append(r) 

209 continue 

210 futures[w.submit(_augment, r)] = r 

211 

212 for f in as_completed(futures): 

213 if f.exception(): 

214 log.warning("Error getting policy for:%s err:%s", 

215 r['FunctionName'], f.exception()) 

216 r = futures[f] 

217 continue 

218 results.append(f.result()) 

219 

220 return filter(None, results) 

221 

222 

223@AWSLambda.filter_registry.register('event-source') 

224class LambdaEventSource(ValueFilter): 

225 # this uses iam policy, it should probably use 

226 # event source mapping api 

227 

228 annotation_key = "c7n:EventSources" 

229 schema = type_schema('event-source', rinherit=ValueFilter.schema) 

230 schema_alias = False 

231 permissions = ('lambda:GetPolicy',) 

232 

233 def process(self, resources, event=None): 

234 client = local_session(self.manager.session_factory).client('lambda') 

235 self.log.debug("fetching policy for %d lambdas" % len(resources)) 

236 resources = get_lambda_policies( 

237 client, self.executor_factory, resources, self.log) 

238 self.data['key'] = self.annotation_key 

239 return super(LambdaEventSource, self).process(resources, event) 

240 

241 def __call__(self, r): 

242 if 'c7n:Policy' not in r: 

243 return False 

244 sources = set() 

245 data = json.loads(r['c7n:Policy']) 

246 for s in data.get('Statement', ()): 

247 if s['Effect'] != 'Allow': 

248 continue 

249 if 'Service' in s['Principal']: 

250 sources.add(s['Principal']['Service']) 

251 if sources: 

252 r[self.annotation_key] = list(sources) 

253 return self.match(r) 

254 

255 

256@AWSLambda.filter_registry.register('cross-account') 

257class LambdaCrossAccountAccessFilter(CrossAccountAccessFilter): 

258 """Filters lambda functions with cross-account permissions 

259 

260 The whitelist parameter can be used to prevent certain accounts 

261 from being included in the results (essentially stating that these 

262 accounts permissions are allowed to exist) 

263 

264 This can be useful when combining this filter with the delete action. 

265 

266 :example: 

267 

268 .. code-block:: yaml 

269 

270 policies: 

271 - name: lambda-cross-account 

272 resource: lambda 

273 filters: 

274 - type: cross-account 

275 whitelist: 

276 - 'IAM-Policy-Cross-Account-Access' 

277 

278 """ 

279 permissions = ('lambda:GetPolicy',) 

280 

281 policy_attribute = 'c7n:Policy' 

282 

283 def process(self, resources, event=None): 

284 client = local_session(self.manager.session_factory).client('lambda') 

285 self.log.debug("fetching policy for %d lambdas" % len(resources)) 

286 resources = get_lambda_policies( 

287 client, self.executor_factory, resources, self.log) 

288 return super(LambdaCrossAccountAccessFilter, self).process( 

289 resources, event) 

290 

291 

292@AWSLambda.filter_registry.register('kms-key') 

293class KmsFilter(KmsRelatedFilter): 

294 

295 RelatedIdsExpression = 'KMSKeyArn' 

296 

297 

298@AWSLambda.filter_registry.register('has-specific-managed-policy') 

299class HasSpecificManagedPolicy(SpecificIamRoleManagedPolicy): 

300 """Filter an lambda function that has an IAM execution role that has a 

301 specific managed IAM policy. 

302 

303 :example: 

304 

305 .. code-block:: yaml 

306 

307 policies: 

308 - name: lambda-has-admin-policy 

309 resource: aws.lambda 

310 filters: 

311 - type: has-specific-managed-policy 

312 value: admin-policy 

313 

314 """ 

315 

316 permissions = ('iam:ListAttachedRolePolicies',) 

317 

318 def process(self, resources, event=None): 

319 client = utils.local_session(self.manager.session_factory).client('iam') 

320 

321 results = [] 

322 roles = { 

323 r['Role']: { 

324 'RoleName': r['Role'].split('/')[-1] 

325 } 

326 for r in resources 

327 } 

328 

329 for role in roles.values(): 

330 self.get_managed_policies(client, [role]) 

331 for r in resources: 

332 role_arn = r['Role'] 

333 matched_keys = [k for k in roles[role_arn][self.annotation_key] if self.match(k)] 

334 self.merge_annotation(role, self.matched_annotation_key, matched_keys) 

335 if matched_keys: 

336 results.append(r) 

337 

338 return results 

339 

340 

341@AWSLambda.action_registry.register('update') 

342class UpdateLambda(Action): 

343 """Update a lambda's configuration. 

344 

345 This action also has specific support for enacting recommendations 

346 from the AWS Cost Optimization Hub for resizing. 

347 

348 :example: 

349 

350 .. code-block:: yaml 

351 

352 policies: 

353 - name: lambda-rightsize 

354 resource: aws.lambda 

355 filters: 

356 - type: cost-optimization 

357 attrs: 

358 - actionType: Rightsize 

359 actions: 

360 - update 

361 

362 """ 

363 schema = type_schema('update', properties={'type': 'object'}) 

364 permissions = ("lambda:UpdateFunctionConfiguration",) 

365 

366 def validate(self): 

367 props = self.data.get('properties', {}) 

368 props['FunctionName'] = 'validation' 

369 shape_validate(props, 'UpdateFunctionConfigurationRequest', 'lambda') 

370 

371 def process(self, resources): 

372 client = utils.local_session(self.manager.session_factory).client('lambda') 

373 retry = get_retry(('TooManyRequestsException', 'ResourceConflictException')) 

374 

375 for r in resources: 

376 params = self.get_parameters(r) 

377 params.pop('FunctionName', None) 

378 try: 

379 retry( 

380 client.update_function_configuration, 

381 FunctionName=r['FunctionName'], 

382 **params 

383 ) 

384 except client.exceptions.ResourceNotFoundException: 

385 continue 

386 

387 def get_parameters(self, r): 

388 params = self.data.get('properties', {}) 

389 hub_recommendation = r.get(CostHubRecommendation.annotation_key) 

390 if hub_recommendation and hub_recommendation['actionType'] == 'Rightsize': 

391 size = int(hub_recommendation['recommendedResourceSummary'].split(' ')[0]) 

392 params['MemorySize'] = size 

393 return params 

394 

395 

396@AWSLambda.action_registry.register('set-xray-tracing') 

397class LambdaEnableXrayTracing(Action): 

398 """ 

399 This action allows for enable Xray tracing to Active 

400 

401 :example: 

402 

403 .. code-block:: yaml 

404 

405 actions: 

406 - type: enable-xray-tracing 

407 """ 

408 

409 schema = type_schema( 

410 'set-xray-tracing', 

411 **{'state': {'default': True, 'type': 'boolean'}} 

412 ) 

413 permissions = ("lambda:UpdateFunctionConfiguration",) 

414 

415 def get_mode_val(self, state): 

416 if state: 

417 return "Active" 

418 return "PassThrough" 

419 

420 def process(self, resources): 

421 """ 

422 Enables the Xray Tracing for the function. 

423 

424 Args: 

425 resources: AWS lamdba resources 

426 Returns: 

427 None 

428 """ 

429 config = Config( 

430 retries={ 

431 'max_attempts': 8, 

432 'mode': 'standard' 

433 } 

434 ) 

435 client = local_session(self.manager.session_factory).client('lambda', config=config) 

436 updateState = self.data.get('state', True) 

437 retry = get_retry(('TooManyRequestsException', 'ResourceConflictException')) 

438 

439 mode = self.get_mode_val(updateState) 

440 for resource in resources: 

441 state = bool(resource["TracingConfig"]["Mode"] == "Active") 

442 if updateState != state: 

443 function_name = resource["FunctionName"] 

444 self.log.info(f"Set Xray tracing to {mode} for lambda {function_name}") 

445 try: 

446 retry( 

447 client.update_function_configuration, 

448 FunctionName=function_name, 

449 TracingConfig={ 

450 'Mode': mode 

451 } 

452 ) 

453 except client.exceptions.ResourceNotFoundException: 

454 continue 

455 

456 

457@AWSLambda.action_registry.register('post-finding') 

458class LambdaPostFinding(PostFinding): 

459 

460 resource_type = 'AwsLambdaFunction' 

461 

462 def format_resource(self, r): 

463 envelope, payload = self.format_envelope(r) 

464 # security hub formatting beggars belief 

465 details = self.filter_empty(select_keys(r, 

466 ['CodeSha256', 

467 'DeadLetterConfig', 

468 'Environment', 

469 'Handler', 

470 'LastModified', 

471 'MemorySize', 

472 'MasterArn', 

473 'RevisionId', 

474 'Role', 

475 'Runtime', 

476 'TracingConfig', 

477 'Timeout', 

478 'Version', 

479 'VpcConfig'])) 

480 # check and set the correct formatting value for kms key arn if it exists 

481 kms_value = r.get('KMSKeyArn') 

482 if kms_value is not None: 

483 details['KmsKeyArn'] = kms_value 

484 # do the brain dead parts Layers, Code, TracingConfig 

485 if 'Layers' in r: 

486 r['Layers'] = { 

487 'Arn': r['Layers'][0]['Arn'], 

488 'CodeSize': r['Layers'][0]['CodeSize']} 

489 details.get('VpcConfig', {}).pop('VpcId', None) 

490 

491 if 'Code' in r and r['Code'].get('RepositoryType') == "S3": 

492 parsed = urlparse(r['Code']['Location']) 

493 details['Code'] = { 

494 'S3Bucket': parsed.netloc.split('.', 1)[0], 

495 'S3Key': parsed.path[1:]} 

496 params = parse_qs(parsed.query) 

497 if params['versionId']: 

498 details['Code']['S3ObjectVersion'] = params['versionId'][0] 

499 payload.update(details) 

500 return envelope 

501 

502 

503@AWSLambda.action_registry.register('trim-versions') 

504class VersionTrim(Action): 

505 """Delete old versions of a function. 

506 

507 By default this will only remove the non $LATEST 

508 version of a function that are not referenced by 

509 an alias. Optionally it can delete only versions 

510 older than a given age. 

511 

512 :example: 

513 

514 .. code-block:: yaml 

515 

516 policies: 

517 - name: lambda-gc 

518 resource: aws.lambda 

519 actions: 

520 - type: trim-versions 

521 exclude-aliases: true # default true 

522 older-than: 60 # default not-set 

523 retain-latest: true # default false 

524 

525 retain-latest refers to whether the latest numeric 

526 version will be retained, the $LATEST alias will 

527 still point to the last revision even without this set, 

528 so this is safe wrt to the function availability, its more 

529 about desire to retain an explicit version of the current 

530 code, rather than just the $LATEST alias pointer which will 

531 be automatically updated. 

532 """ 

533 permissions = ('lambda:ListAliases', 'lambda:ListVersionsByFunction', 

534 'lambda:DeleteFunction',) 

535 

536 schema = type_schema( 

537 'trim-versions', 

538 **{'exclude-aliases': {'default': True, 'type': 'boolean'}, 

539 'retain-latest': {'default': True, 'type': 'boolean'}, 

540 'older-than': {'type': 'number'}}) 

541 

542 def process(self, resources): 

543 client = local_session(self.manager.session_factory).client('lambda') 

544 matched = total = 0 

545 for r in resources: 

546 fmatched, ftotal = self.process_lambda(client, r) 

547 matched += fmatched 

548 total += ftotal 

549 self.log.info('trim-versions cleaned %s of %s lambda storage' % ( 

550 get_human_size(matched), get_human_size(total))) 

551 

552 def get_aliased_versions(self, client, r): 

553 aliases_pager = client.get_paginator('list_aliases') 

554 aliases_pager.PAGE_ITERATOR_CLASS = query.RetryPageIterator 

555 aliases = aliases_pager.paginate( 

556 FunctionName=r['FunctionName']).build_full_result().get('Aliases') 

557 

558 aliased_versions = set() 

559 for a in aliases: 

560 aliased_versions.add("%s:%s" % ( 

561 a['AliasArn'].rsplit(':', 1)[0], a['FunctionVersion'])) 

562 return aliased_versions 

563 

564 def process_lambda(self, client, r): 

565 exclude_aliases = self.data.get('exclude-aliases', True) 

566 retain_latest = self.data.get('retain-latest', False) 

567 date_threshold = self.data.get('older-than') 

568 date_threshold = ( 

569 date_threshold and 

570 parse_date(datetime.utcnow()) - timedelta(days=date_threshold) or 

571 None) 

572 aliased_versions = () 

573 

574 if exclude_aliases: 

575 aliased_versions = self.get_aliased_versions(client, r) 

576 

577 versions_pager = client.get_paginator('list_versions_by_function') 

578 versions_pager.PAGE_ITERATOR_CLASS = query.RetryPageIterator 

579 pager = versions_pager.paginate(FunctionName=r['FunctionName']) 

580 

581 matched = total = 0 

582 latest_sha = None 

583 

584 for page in pager: 

585 versions = page.get('Versions') 

586 for v in versions: 

587 if v['Version'] == '$LATEST': 

588 latest_sha = v['CodeSha256'] 

589 continue 

590 total += v['CodeSize'] 

591 if v['FunctionArn'] in aliased_versions: 

592 continue 

593 if date_threshold and parse_date(v['LastModified']) > date_threshold: 

594 continue 

595 # Retain numbered version, not required, but it feels like a good thing 

596 # to do. else the latest alias will still point. 

597 if retain_latest and latest_sha and v['CodeSha256'] == latest_sha: 

598 continue 

599 matched += v['CodeSize'] 

600 self.manager.retry( 

601 client.delete_function, FunctionName=v['FunctionArn']) 

602 return (matched, total) 

603 

604 

605@AWSLambda.action_registry.register('remove-statements') 

606class RemovePolicyStatement(RemovePolicyBase): 

607 """Action to remove policy/permission statements from lambda functions. 

608 

609 :example: 

610 

611 .. code-block:: yaml 

612 

613 policies: 

614 - name: lambda-remove-cross-accounts 

615 resource: lambda 

616 filters: 

617 - type: cross-account 

618 actions: 

619 - type: remove-statements 

620 statement_ids: matched 

621 """ 

622 

623 schema = type_schema( 

624 'remove-statements', 

625 required=['statement_ids'], 

626 statement_ids={'oneOf': [ 

627 {'enum': ['matched']}, 

628 {'type': 'array', 'items': {'type': 'string'}}]}) 

629 

630 permissions = ("lambda:GetPolicy", "lambda:RemovePermission") 

631 

632 def process(self, resources): 

633 results = [] 

634 client = local_session(self.manager.session_factory).client('lambda') 

635 for r in resources: 

636 try: 

637 if self.process_resource(client, r): 

638 results.append(r) 

639 except Exception: 

640 self.log.exception( 

641 "Error processing lambda %s", r['FunctionArn']) 

642 return results 

643 

644 def process_resource(self, client, resource): 

645 if 'c7n:Policy' not in resource: 

646 try: 

647 resource['c7n:Policy'] = client.get_policy( 

648 FunctionName=resource['FunctionName']).get('Policy') 

649 except ClientError as e: 

650 if e.response['Error']['Code'] != ErrAccessDenied: 

651 raise 

652 resource['c7n:Policy'] = None 

653 

654 if not resource['c7n:Policy']: 

655 return 

656 

657 p = json.loads(resource['c7n:Policy']) 

658 

659 _, found = self.process_policy( 

660 p, resource, CrossAccountAccessFilter.annotation_key) 

661 if not found: 

662 return 

663 

664 for f in found: 

665 client.remove_permission( 

666 FunctionName=resource['FunctionName'], 

667 StatementId=f['Sid']) 

668 

669 

670@AWSLambda.action_registry.register('set-concurrency') 

671class SetConcurrency(Action): 

672 """Set lambda function concurrency to the desired level. 

673 

674 Can be used to set the reserved function concurrency to an exact value, 

675 to delete reserved concurrency, or to set the value to an attribute of 

676 the resource. 

677 """ 

678 

679 schema = type_schema( 

680 'set-concurrency', 

681 required=('value',), 

682 **{'expr': {'type': 'boolean'}, 

683 'value': {'oneOf': [ 

684 {'type': 'string'}, 

685 {'type': 'integer'}, 

686 {'type': 'null'}]}}) 

687 

688 permissions = ('lambda:DeleteFunctionConcurrency', 

689 'lambda:PutFunctionConcurrency') 

690 

691 def validate(self): 

692 if self.data.get('expr', False) and not isinstance(self.data['value'], str): 

693 raise ValueError("invalid value expression %s" % self.data['value']) 

694 return self 

695 

696 def process(self, functions): 

697 client = local_session(self.manager.session_factory).client('lambda') 

698 is_expr = self.data.get('expr', False) 

699 value = self.data['value'] 

700 if is_expr: 

701 value = jmespath_compile(value) 

702 

703 none_type = type(None) 

704 

705 for function in functions: 

706 fvalue = value 

707 if is_expr: 

708 fvalue = value.search(function) 

709 if isinstance(fvalue, float): 

710 fvalue = int(fvalue) 

711 if isinstance(value, int) or isinstance(value, none_type): 

712 self.policy.log.warning( 

713 "Function: %s Invalid expression value for concurrency: %s", 

714 function['FunctionName'], fvalue) 

715 continue 

716 if fvalue is None: 

717 client.delete_function_concurrency( 

718 FunctionName=function['FunctionName']) 

719 else: 

720 client.put_function_concurrency( 

721 FunctionName=function['FunctionName'], 

722 ReservedConcurrentExecutions=fvalue) 

723 

724 

725@AWSLambda.action_registry.register('delete') 

726class Delete(Action): 

727 """Delete a lambda function (including aliases and older versions). 

728 

729 :example: 

730 

731 .. code-block:: yaml 

732 

733 policies: 

734 - name: lambda-delete-dotnet-functions 

735 resource: lambda 

736 filters: 

737 - Runtime: dotnetcore1.0 

738 actions: 

739 - delete 

740 """ 

741 schema = type_schema('delete') 

742 permissions = ("lambda:DeleteFunction",) 

743 

744 def process(self, functions): 

745 client = local_session(self.manager.session_factory).client('lambda') 

746 for function in functions: 

747 try: 

748 client.delete_function(FunctionName=function['FunctionName']) 

749 except ClientError as e: 

750 if e.response['Error']['Code'] == "ResourceNotFoundException": 

751 continue 

752 raise 

753 self.log.debug("Deleted %d functions", len(functions)) 

754 

755 

756@AWSLambda.action_registry.register('modify-security-groups') 

757class LambdaModifyVpcSecurityGroups(ModifyVpcSecurityGroupsAction): 

758 

759 permissions = ("lambda:UpdateFunctionConfiguration",) 

760 

761 def process(self, functions): 

762 client = local_session(self.manager.session_factory).client('lambda') 

763 groups = super(LambdaModifyVpcSecurityGroups, self).get_groups( 

764 functions) 

765 

766 for idx, i in enumerate(functions): 

767 if 'VpcConfig' not in i: # only continue if Lambda func is VPC-enabled 

768 continue 

769 try: 

770 client.update_function_configuration(FunctionName=i['FunctionName'], 

771 VpcConfig={'SecurityGroupIds': groups[idx]}) 

772 except client.exceptions.ResourceNotFoundException: 

773 continue 

774 

775 

776@resources.register('lambda-layer') 

777class LambdaLayerVersion(query.QueryResourceManager): 

778 """Note custodian models the lambda layer version. 

779 

780 Layers end up being a logical asset, the physical asset for use 

781 and management is the layer verison. 

782 

783 To ease that distinction, we support querying just the latest 

784 layer version or having a policy against all layer versions. 

785 

786 By default we query all versions, the following is an example 

787 to query just the latest. 

788 

789 .. code-block:: yaml 

790 

791 policies: 

792 - name: lambda-layer 

793 resource: lambda 

794 query: 

795 - version: latest 

796 

797 """ 

798 

799 class resource_type(query.TypeInfo): 

800 service = 'lambda' 

801 enum_spec = ('list_layers', 'Layers', None) 

802 name = id = 'LayerName' 

803 date = 'CreatedDate' 

804 arn = "LayerVersionArn" 

805 arn_type = "layer" 

806 cfn_type = 'AWS::Lambda::LayerVersion' 

807 

808 def augment(self, resources): 

809 versions = {} 

810 for r in resources: 

811 versions[r['LayerName']] = v = r['LatestMatchingVersion'] 

812 v['LayerName'] = r['LayerName'] 

813 

814 if {'version': 'latest'} in self.data.get('query', []): 

815 return list(versions.values()) 

816 

817 layer_names = list(versions) 

818 client = local_session(self.session_factory).client('lambda') 

819 

820 versions = [] 

821 for layer_name in layer_names: 

822 pager = get_layer_version_paginator(client) 

823 for v in pager.paginate( 

824 LayerName=layer_name).build_full_result().get('LayerVersions'): 

825 v['LayerName'] = layer_name 

826 versions.append(v) 

827 return versions 

828 

829 

830def get_layer_version_paginator(client): 

831 pager = Paginator( 

832 client.list_layer_versions, 

833 {'input_token': 'NextToken', 

834 'output_token': 'NextToken', 

835 'result_key': 'LayerVersions'}, 

836 client.meta.service_model.operation_model('ListLayerVersions')) 

837 pager.PAGE_ITERATOR_CLS = query.RetryPageIterator 

838 return pager 

839 

840 

841@LambdaLayerVersion.filter_registry.register('cross-account') 

842class LayerCrossAccount(CrossAccountAccessFilter): 

843 

844 permissions = ('lambda:GetLayerVersionPolicy',) 

845 

846 def process(self, resources, event=None): 

847 client = local_session(self.manager.session_factory).client('lambda') 

848 for r in resources: 

849 if 'c7n:Policy' in r: 

850 continue 

851 try: 

852 rpolicy = self.manager.retry( 

853 client.get_layer_version_policy, 

854 LayerName=r['LayerName'], 

855 VersionNumber=r['Version']).get('Policy') 

856 except client.exceptions.ResourceNotFoundException: 

857 rpolicy = {} 

858 r['c7n:Policy'] = rpolicy 

859 return super(LayerCrossAccount, self).process(resources) 

860 

861 def get_resource_policy(self, r): 

862 return r['c7n:Policy'] 

863 

864 

865@LambdaLayerVersion.action_registry.register('remove-statements') 

866class LayerRemovePermissions(RemovePolicyBase): 

867 

868 schema = type_schema( 

869 'remove-statements', 

870 required=['statement_ids'], 

871 statement_ids={'oneOf': [ 

872 {'enum': ['matched']}, 

873 {'type': 'array', 'items': {'type': 'string'}}]}) 

874 

875 permissions = ( 

876 "lambda:GetLayerVersionPolicy", 

877 "lambda:RemoveLayerVersionPermission") 

878 

879 def process(self, resources): 

880 client = local_session(self.manager.session_factory).client('lambda') 

881 for r in resources: 

882 self.process_resource(client, r) 

883 

884 def process_resource(self, client, r): 

885 if 'c7n:Policy' not in r: 

886 try: 

887 r['c7n:Policy'] = self.manager.retry( 

888 client.get_layer_version_policy, 

889 LayerName=r['LayerName'], 

890 VersionNumber=r['Version']) 

891 except client.exceptions.ResourceNotFound: 

892 return 

893 

894 p = json.loads(r['c7n:Policy']) 

895 

896 _, found = self.process_policy( 

897 p, r, CrossAccountAccessFilter.annotation_key) 

898 

899 if not found: 

900 return 

901 

902 for f in found: 

903 self.manager.retry( 

904 client.remove_layer_version_permission, 

905 LayerName=r['LayerName'], 

906 StatementId=f['Sid'], 

907 VersionNumber=r['Version']) 

908 

909 

910@LambdaLayerVersion.action_registry.register('delete') 

911class DeleteLayerVersion(Action): 

912 

913 schema = type_schema('delete') 

914 permissions = ('lambda:DeleteLayerVersion',) 

915 

916 def process(self, resources): 

917 client = local_session( 

918 self.manager.session_factory).client('lambda') 

919 

920 for r in resources: 

921 try: 

922 self.manager.retry( 

923 client.delete_layer_version, 

924 LayerName=r['LayerName'], 

925 VersionNumber=r['Version']) 

926 except client.exceptions.ResourceNotFound: 

927 continue 

928 

929 

930@LambdaLayerVersion.action_registry.register('post-finding') 

931class LayerPostFinding(PostFinding): 

932 

933 resource_type = 'AwsLambdaLayerVersion' 

934 

935 def format_resource(self, r): 

936 envelope, payload = self.format_envelope(r) 

937 payload.update(self.filter_empty( 

938 select_keys(r, ['Version', 'CreatedDate', 'CompatibleRuntimes']))) 

939 return envelope 

940 

941 

942@AWSLambda.filter_registry.register('lambda-edge') 

943class LambdaEdgeFilter(Filter): 

944 """ 

945 Filter for lambda@edge functions. Lambda@edge only exists in us-east-1 

946 

947 :example: 

948 

949 .. code-block:: yaml 

950 

951 policies: 

952 - name: lambda-edge-filter 

953 resource: lambda 

954 region: us-east-1 

955 filters: 

956 - type: lambda-edge 

957 state: True 

958 """ 

959 permissions = ('cloudfront:ListDistributions',) 

960 

961 schema = type_schema('lambda-edge', 

962 **{'state': {'type': 'boolean'}}) 

963 

964 def get_lambda_cf_map(self): 

965 cfs = self.manager.get_resource_manager('distribution').resources() 

966 func_expressions = ('DefaultCacheBehavior.LambdaFunctionAssociations.Items', 

967 'CacheBehaviors.Items[].LambdaFunctionAssociations.Items[]') 

968 lambda_dist_map = {} 

969 for d in cfs: 

970 for exp in func_expressions: 

971 if jmespath_search(exp, d): 

972 for function in jmespath_search(exp, d): 

973 # Geting rid of the version number in the arn 

974 lambda_edge_arn = ':'.join(function['LambdaFunctionARN'].split(':')[:-1]) 

975 lambda_dist_map.setdefault(lambda_edge_arn, set()).add(d['Id']) 

976 return lambda_dist_map 

977 

978 def process(self, resources, event=None): 

979 results = [] 

980 if self.manager.config.region != 'us-east-1' and self.data.get('state'): 

981 return [] 

982 annotation_key = 'c7n:DistributionIds' 

983 lambda_edge_cf_map = self.get_lambda_cf_map() 

984 for r in resources: 

985 if (r['FunctionArn'] in lambda_edge_cf_map and self.data.get('state')): 

986 r[annotation_key] = list(lambda_edge_cf_map.get(r['FunctionArn'])) 

987 results.append(r) 

988 elif (r['FunctionArn'] not in lambda_edge_cf_map and not self.data.get('state')): 

989 results.append(r) 

990 return results