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

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.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 

31 

32ErrAccessDenied = "AccessDeniedException" 

33 

34 

35class DescribeLambda(query.DescribeSource): 

36 

37 def augment(self, resources): 

38 return universal_augment( 

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

40 

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 

56 

57 

58class ConfigLambda(query.ConfigSource): 

59 

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 

65 

66 

67@resources.register('lambda') 

68class AWSLambda(query.QueryResourceManager): 

69 

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() 

82 

83 source_mapping = { 

84 'describe': DescribeLambda, 

85 'config': ConfigLambda 

86 } 

87 

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

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

90 

91 

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

93class SecurityGroupFilter(net_filters.SecurityGroupFilter): 

94 

95 RelatedIdsExpression = "VpcConfig.SecurityGroupIds[]" 

96 

97 

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

99class SubnetFilter(net_filters.SubnetFilter): 

100 

101 RelatedIdsExpression = "VpcConfig.SubnetIds[]" 

102 

103 

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

105class VpcFilter(net_filters.VpcFilter): 

106 

107 RelatedIdsExpression = "VpcConfig.VpcId" 

108 

109 

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

111 

112 

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

114class LambdaPermissions(CheckPermissions): 

115 

116 def get_iam_arns(self, resources): 

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

118 

119 

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

121class ReservedConcurrency(ValueFilter): 

122 

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',) 

128 

129 def validate(self): 

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

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

132 

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

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

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

136 

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 

149 

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) 

153 

154 

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

156 

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 

169 

170 results = [] 

171 futures = {} 

172 

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 

179 

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()) 

187 

188 return filter(None, results) 

189 

190 

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

192class LambdaEventSource(ValueFilter): 

193 # this uses iam policy, it should probably use 

194 # event source mapping api 

195 

196 annotation_key = "c7n:EventSources" 

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

198 schema_alias = False 

199 permissions = ('lambda:GetPolicy',) 

200 

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) 

208 

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) 

222 

223 

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

225class LambdaCrossAccountAccessFilter(CrossAccountAccessFilter): 

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

227 

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) 

231 

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

233 

234 :example: 

235 

236 .. code-block:: yaml 

237 

238 policies: 

239 - name: lambda-cross-account 

240 resource: lambda 

241 filters: 

242 - type: cross-account 

243 whitelist: 

244 - 'IAM-Policy-Cross-Account-Access' 

245 

246 """ 

247 permissions = ('lambda:GetPolicy',) 

248 

249 policy_attribute = 'c7n:Policy' 

250 

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) 

258 

259 

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

261class KmsFilter(KmsRelatedFilter): 

262 

263 RelatedIdsExpression = 'KMSKeyArn' 

264 

265 

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. 

270 

271 :example: 

272 

273 .. code-block:: yaml 

274 

275 policies: 

276 - name: lambda-has-admin-policy 

277 resource: aws.lambda 

278 filters: 

279 - type: has-specific-managed-policy 

280 value: admin-policy 

281 

282 """ 

283 

284 permissions = ('iam:ListAttachedRolePolicies',) 

285 

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

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

288 

289 results = [] 

290 roles = { 

291 r['Role']: { 

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

293 } 

294 for r in resources 

295 } 

296 

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) 

305 

306 return results 

307 

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

309class LambdaEnableXrayTracing(Action): 

310 """ 

311 This action allows for enable Xray tracing to Active 

312 

313 :example: 

314 

315 .. code-block:: yaml 

316 

317 actions: 

318 - type: enable-xray-tracing 

319 """ 

320 

321 schema = type_schema( 

322 'set-xray-tracing', 

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

324 ) 

325 permissions = ("lambda:UpdateFunctionConfiguration",) 

326 

327 def get_mode_val(self, state): 

328 if state: 

329 return "Active" 

330 return "PassThrough" 

331 

332 def process(self, resources): 

333 """ 

334 Enables the Xray Tracing for the function. 

335 

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')) 

350 

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 

367 

368 

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

370class LambdaPostFinding(PostFinding): 

371 

372 resource_type = 'AwsLambdaFunction' 

373 

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) 

402 

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 

413 

414 

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

416class VersionTrim(Action): 

417 """Delete old versions of a function. 

418 

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. 

423 

424 :example: 

425 

426 .. code-block:: yaml 

427 

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 

436 

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',) 

447 

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'}}) 

453 

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))) 

463 

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') 

469 

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 

475 

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 = () 

485 

486 if exclude_aliases: 

487 aliased_versions = self.get_aliased_versions(client, r) 

488 

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']) 

492 

493 matched = total = 0 

494 latest_sha = None 

495 

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) 

515 

516 

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

518class RemovePolicyStatement(RemovePolicyBase): 

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

520 

521 :example: 

522 

523 .. code-block:: yaml 

524 

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 """ 

534 

535 schema = type_schema( 

536 'remove-statements', 

537 required=['statement_ids'], 

538 statement_ids={'oneOf': [ 

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

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

541 

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

543 

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 

555 

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 

565 

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

567 return 

568 

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

570 

571 statements, found = self.process_policy( 

572 p, resource, CrossAccountAccessFilter.annotation_key) 

573 if not found: 

574 return 

575 

576 for f in found: 

577 client.remove_permission( 

578 FunctionName=resource['FunctionName'], 

579 StatementId=f['Sid']) 

580 

581 

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

583class SetConcurrency(Action): 

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

585 

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 """ 

590 

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'}]}}) 

599 

600 permissions = ('lambda:DeleteFunctionConcurrency', 

601 'lambda:PutFunctionConcurrency') 

602 

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 

607 

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) 

614 

615 none_type = type(None) 

616 

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) 

635 

636 

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

638class Delete(Action): 

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

640 

641 :example: 

642 

643 .. code-block:: yaml 

644 

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",) 

655 

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)) 

666 

667 

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

669class LambdaModifyVpcSecurityGroups(ModifyVpcSecurityGroupsAction): 

670 

671 permissions = ("lambda:UpdateFunctionConfiguration",) 

672 

673 def process(self, functions): 

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

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

676 functions) 

677 

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 

686 

687 

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

689class LambdaLayerVersion(query.QueryResourceManager): 

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

691 

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

693 and management is the layer verison. 

694 

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

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

697 

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

699 to query just the latest. 

700 

701 .. code-block:: yaml 

702 

703 policies: 

704 - name: lambda-layer 

705 resource: lambda 

706 query: 

707 - version: latest 

708 

709 """ 

710 

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' 

719 

720 def augment(self, resources): 

721 versions = {} 

722 for r in resources: 

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

724 v['LayerName'] = r['LayerName'] 

725 

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

727 return list(versions.values()) 

728 

729 layer_names = list(versions) 

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

731 

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 

740 

741 

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 

751 

752 

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

754class LayerCrossAccount(CrossAccountAccessFilter): 

755 

756 permissions = ('lambda:GetLayerVersionPolicy',) 

757 

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) 

772 

773 def get_resource_policy(self, r): 

774 return r['c7n:Policy'] 

775 

776 

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

778class LayerRemovePermissions(RemovePolicyBase): 

779 

780 schema = type_schema( 

781 'remove-statements', 

782 required=['statement_ids'], 

783 statement_ids={'oneOf': [ 

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

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

786 

787 permissions = ( 

788 "lambda:GetLayerVersionPolicy", 

789 "lambda:RemoveLayerVersionPermission") 

790 

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) 

795 

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 

805 

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

807 

808 statements, found = self.process_policy( 

809 p, r, CrossAccountAccessFilter.annotation_key) 

810 

811 if not found: 

812 return 

813 

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']) 

820 

821 

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

823class DeleteLayerVersion(Action): 

824 

825 schema = type_schema('delete') 

826 permissions = ('lambda:DeleteLayerVersion',) 

827 

828 def process(self, resources): 

829 client = local_session( 

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

831 

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 

840 

841 

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

843class LayerPostFinding(PostFinding): 

844 

845 resource_type = 'AwsLambdaLayerVersion' 

846 

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 

852 

853 

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

855 

856class LambdaEdgeFilter(Filter): 

857 """ 

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

859 

860 :example: 

861 

862 .. code-block:: yaml 

863 

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',) 

873 

874 schema = type_schema('lambda-edge', 

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

876 

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 

890 

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