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

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

533 statements  

1# Copyright The Cloud Custodian Authors. 

2# SPDX-License-Identifier: Apache-2.0 

3""" 

4Application & Network Load Balancers 

5""" 

6import json 

7import logging 

8import re 

9 

10from botocore.exceptions import ClientError 

11from collections import defaultdict 

12from c7n.actions import ActionRegistry, BaseAction, ModifyVpcSecurityGroupsAction 

13from c7n.exceptions import PolicyValidationError 

14from c7n.filters import ( 

15 Filter, 

16 FilterRegistry, 

17 ListItemFilter, 

18 MetricsFilter, 

19 ValueFilter, 

20 WafV2FilterBase, 

21 WafClassicRegionalFilterBase 

22) 

23import c7n.filters.vpc as net_filters 

24from c7n import tags 

25from c7n.manager import resources 

26 

27from c7n.query import QueryResourceManager, DescribeSource, ConfigSource, TypeInfo 

28from c7n.utils import ( 

29 local_session, chunks, type_schema, get_retry, set_annotation) 

30 

31from c7n.resources.aws import Arn 

32from c7n.resources.shield import IsShieldProtected, SetShieldProtection 

33 

34log = logging.getLogger('custodian.app-elb') 

35 

36 

37class DescribeAppElb(DescribeSource): 

38 

39 def get_resources(self, ids, cache=True): 

40 """Support server side filtering on arns or names 

41 """ 

42 if ids[0].startswith('arn:'): 

43 params = {'LoadBalancerArns': ids} 

44 else: 

45 params = {'Names': ids} 

46 return self.query.filter(self.manager, **params) 

47 

48 def augment(self, albs): 

49 _describe_appelb_tags( 

50 albs, 

51 self.manager.session_factory, 

52 self.manager.executor_factory, 

53 self.manager.retry) 

54 

55 return albs 

56 

57 

58class ConfigAppElb(ConfigSource): 

59 

60 def load_resource(self, item): 

61 resource = super(ConfigAppElb, self).load_resource(item) 

62 item_attrs = item['supplementaryConfiguration'][ 

63 'LoadBalancerAttributes'] 

64 if isinstance(item_attrs, str): 

65 item_attrs = json.loads(item_attrs) 

66 # Matches annotation of AppELBAttributeFilterBase filter 

67 resource['Attributes'] = { 

68 attr['key']: parse_attribute_value(attr['value']) for 

69 attr in item_attrs} 

70 return resource 

71 

72 

73@resources.register('app-elb') 

74class AppELB(QueryResourceManager): 

75 """Resource manager for v2 ELBs (AKA ALBs and NLBs). 

76 """ 

77 

78 class resource_type(TypeInfo): 

79 service = 'elbv2' 

80 permission_prefix = 'elasticloadbalancing' 

81 enum_spec = ('describe_load_balancers', 'LoadBalancers', None) 

82 name = 'LoadBalancerName' 

83 id = 'LoadBalancerArn' 

84 filter_name = "Names" 

85 filter_type = "list" 

86 dimension = "LoadBalancer" 

87 date = 'CreatedTime' 

88 cfn_type = config_type = 'AWS::ElasticLoadBalancingV2::LoadBalancer' 

89 arn = "LoadBalancerArn" 

90 # The suffix varies by type of loadbalancer (app vs net) 

91 arn_type = 'loadbalancer/app' 

92 permissions_augment = ("elasticloadbalancing:DescribeTags",) 

93 

94 retry = staticmethod(get_retry(('Throttling',))) 

95 source_mapping = { 

96 'describe': DescribeAppElb, 

97 'config': ConfigAppElb 

98 } 

99 

100 @classmethod 

101 def get_permissions(cls): 

102 # override as the service is not the iam prefix 

103 return ("elasticloadbalancing:DescribeLoadBalancers", 

104 "elasticloadbalancing:DescribeLoadBalancerAttributes", 

105 "elasticloadbalancing:DescribeTags") 

106 

107 

108def _describe_appelb_tags(albs, session_factory, executor_factory, retry): 

109 client = local_session(session_factory).client('elbv2') 

110 

111 def _process_tags(alb_set): 

112 alb_map = {alb['LoadBalancerArn']: alb for alb in alb_set} 

113 

114 results = retry(client.describe_tags, ResourceArns=list(alb_map.keys())) 

115 for tag_desc in results['TagDescriptions']: 

116 if ('ResourceArn' in tag_desc and 

117 tag_desc['ResourceArn'] in alb_map): 

118 alb_map[tag_desc['ResourceArn']]['Tags'] = tag_desc['Tags'] 

119 

120 with executor_factory(max_workers=2) as w: 

121 list(w.map(_process_tags, chunks(albs, 20))) 

122 

123 

124AppELB.filter_registry.register('tag-count', tags.TagCountFilter) 

125AppELB.filter_registry.register('marked-for-op', tags.TagActionFilter) 

126AppELB.filter_registry.register('shield-enabled', IsShieldProtected) 

127AppELB.filter_registry.register('network-location', net_filters.NetworkLocation) 

128AppELB.action_registry.register('set-shield', SetShieldProtection) 

129 

130 

131@AppELB.filter_registry.register('metrics') 

132class AppElbMetrics(MetricsFilter): 

133 """Filter app/net load balancer by metric values. 

134 

135 Note application and network load balancers use different Cloud 

136 Watch metrics namespaces and metric names, the custodian app-elb 

137 resource returns both types of load balancer, so an additional 

138 filter should be used to ensure only targeting a particular 

139 type. ie. `- Type: application` or `- Type: network` 

140 

141 See available application load balancer metrics here 

142 https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-cloudwatch-metrics.html 

143 

144 See available network load balancer metrics here. 

145 https://docs.aws.amazon.com/elasticloadbalancing/latest/network/load-balancer-cloudwatch-metrics.html 

146 

147 

148 For network load balancer metrics, the metrics filter requires specifying 

149 the namespace parameter to the filter. 

150 

151 .. code-block:: yaml 

152 

153 policies: 

154 - name: net-lb-underutilized 

155 resource: app-elb 

156 filters: 

157 - Type: network 

158 - type: metrics 

159 name: ActiveFlowCount 

160 namespace: AWS/NetworkELB 

161 statistics: Sum 

162 days: 14 

163 value: 100 

164 op: less-than 

165 """ 

166 

167 def get_dimensions(self, resource): 

168 return [{ 

169 'Name': self.model.dimension, 

170 'Value': Arn.parse(resource['LoadBalancerArn']).resource}] 

171 

172 

173@AppELB.filter_registry.register('security-group') 

174class SecurityGroupFilter(net_filters.SecurityGroupFilter): 

175 

176 RelatedIdsExpression = "SecurityGroups[]" 

177 

178 

179@AppELB.filter_registry.register('subnet') 

180class SubnetFilter(net_filters.SubnetFilter): 

181 

182 RelatedIdsExpression = "AvailabilityZones[].SubnetId" 

183 

184 

185@AppELB.filter_registry.register('vpc') 

186class VpcFilter(net_filters.VpcFilter): 

187 

188 RelatedIdsExpression = "VpcId" 

189 

190 

191@AppELB.filter_registry.register('waf-enabled') 

192class WafEnabled(WafClassicRegionalFilterBase): 

193 """Filter Application LoadBalancer by waf-regional web-acl 

194 

195 :example: 

196 

197 .. code-block:: yaml 

198 

199 policies: 

200 - name: filter-elb-waf-regional 

201 resource: app-elb 

202 filters: 

203 - type: waf-enabled 

204 state: false 

205 web-acl: test 

206 """ 

207 

208 # application load balancers don't hold a reference to the associated web acl 

209 # so we have to look them up via the associations on the web acl directly 

210 def get_associated_web_acl(self, resource): 

211 return self.get_web_acl_from_associations( 

212 'APPLICATION_LOAD_BALANCER', 

213 resource['LoadBalancerArn'] 

214 ) 

215 

216 

217@AppELB.filter_registry.register('wafv2-enabled') 

218class WafV2Enabled(WafV2FilterBase): 

219 """Filter Application LoadBalancer by wafv2 web-acl 

220 

221 Supports regex expression for web-acl. 

222 Firewall Manager pushed WebACL's name varies by account and region. 

223 Regex expression can support both local and Firewall Managed WebACL. 

224 

225 :example: 

226 

227 .. code-block:: yaml 

228 

229 policies: 

230 - name: filter-wafv2-elb 

231 resource: app-elb 

232 filters: 

233 - type: wafv2-enabled 

234 state: false 

235 web-acl: testv2 

236 

237 - name: filter-wafv2-elb-regex 

238 resource: app-elb 

239 filters: 

240 - type: wafv2-enabled 

241 state: false 

242 web-acl: .*FMManagedWebACLV2-?FMS-.* 

243 """ 

244 

245 # application load balancers don't hold a reference to the associated web acl 

246 # so we have to look them up via the associations on the web acl directly 

247 def get_associated_web_acl(self, resource): 

248 return self.get_web_acl_from_associations( 

249 'APPLICATION_LOAD_BALANCER', 

250 resource['LoadBalancerArn'] 

251 ) 

252 

253 

254@AppELB.action_registry.register('set-waf') 

255class SetWaf(BaseAction): 

256 """Enable wafv2 protection on Application LoadBalancer. 

257 

258 :example: 

259 

260 .. code-block:: yaml 

261 

262 policies: 

263 - name: set-waf-for-elb 

264 resource: app-elb 

265 filters: 

266 - type: waf-enabled 

267 state: false 

268 web-acl: test 

269 actions: 

270 - type: set-waf 

271 state: true 

272 web-acl: test 

273 

274 - name: disassociate-wafv2-associate-waf-regional-elb 

275 resource: app-elb 

276 filters: 

277 - type: wafv2-enabled 

278 state: true 

279 actions: 

280 - type: set-waf 

281 state: true 

282 web-acl: test 

283 

284 """ 

285 permissions = ('waf-regional:AssociateWebACL', 'waf-regional:ListWebACLs') 

286 

287 schema = type_schema( 

288 'set-waf', required=['web-acl'], **{ 

289 'web-acl': {'type': 'string'}, 

290 # 'force': {'type': 'boolean'}, 

291 'state': {'type': 'boolean'}}) 

292 

293 def validate(self): 

294 found = False 

295 for f in self.manager.iter_filters(): 

296 if isinstance(f, WafEnabled) or isinstance(f, WafV2Enabled): 

297 found = True 

298 break 

299 if not found: 

300 # try to ensure idempotent usage 

301 raise PolicyValidationError( 

302 "set-waf should be used in conjunction with waf-enabled or wafv2-enabled \ 

303 filter on %s" % (self.manager.data,)) 

304 return self 

305 

306 def process(self, resources): 

307 wafs = self.manager.get_resource_manager('waf-regional').resources(augment=False) 

308 name_id_map = {w['Name']: w['WebACLId'] for w in wafs} 

309 target_acl = self.data.get('web-acl') 

310 target_acl_id = name_id_map.get(target_acl, target_acl) 

311 state = self.data.get('state', True) 

312 

313 if state and target_acl_id not in name_id_map.values(): 

314 raise ValueError("invalid web acl: %s" % (target_acl_id)) 

315 

316 client = local_session( 

317 self.manager.session_factory).client('waf-regional') 

318 

319 arn_key = self.manager.resource_type.id 

320 

321 # TODO implement force to reassociate. 

322 # TODO investigate limits on waf association. 

323 for r in resources: 

324 if state: 

325 client.associate_web_acl( 

326 WebACLId=target_acl_id, ResourceArn=r[arn_key]) 

327 else: 

328 client.disassociate_web_acl( 

329 WebACLId=target_acl_id, ResourceArn=r[arn_key]) 

330 

331 

332@AppELB.action_registry.register('set-wafv2') 

333class SetWafV2(BaseAction): 

334 """Enable wafv2 protection on Application LoadBalancer. 

335 

336 Supports regex expression for web-acl 

337 

338 :example: 

339 

340 .. code-block:: yaml 

341 

342 policies: 

343 - name: set-wafv2-for-elb 

344 resource: app-elb 

345 filters: 

346 - type: wafv2-enabled 

347 state: false 

348 web-acl: testv2 

349 actions: 

350 - type: set-wafv2 

351 state: true 

352 web-acl: testv2 

353 

354 - name: disassociate-waf-regional-associate-wafv2-elb 

355 resource: app-elb 

356 filters: 

357 - type: waf-enabled 

358 state: true 

359 actions: 

360 - type: set-wafv2 

361 state: true 

362 

363 policies: 

364 - name: set-wafv2-for-elb-regex 

365 resource: app-elb 

366 filters: 

367 - type: wafv2-enabled 

368 state: false 

369 web-acl: .*FMManagedWebACLV2-?FMS-.* 

370 actions: 

371 - type: set-wafv2 

372 state: true 

373 web-acl: FMManagedWebACLV2-?FMS-TestWebACL 

374 

375 """ 

376 permissions = ('wafv2:AssociateWebACL', 

377 'wafv2:DisassociateWebACL', 

378 'wafv2:ListWebACLs') 

379 

380 schema = type_schema( 

381 'set-wafv2', **{ 

382 'web-acl': {'type': 'string'}, 

383 'state': {'type': 'boolean'}}) 

384 

385 retry = staticmethod(get_retry(( 

386 'ThrottlingException', 

387 'RequestLimitExceeded', 

388 'Throttled', 

389 'ThrottledException', 

390 'Throttling', 

391 'Client.RequestLimitExceeded'))) 

392 

393 def validate(self): 

394 found = False 

395 for f in self.manager.iter_filters(): 

396 if isinstance(f, WafV2Enabled) or isinstance(f, WafEnabled): 

397 found = True 

398 break 

399 if not found: 

400 # try to ensure idempotent usage 

401 raise PolicyValidationError( 

402 "set-wafv2 should be used in conjunction with wafv2-enabled or waf-enabled \ 

403 filter on %s" % (self.manager.data,)) 

404 return self 

405 

406 def process(self, resources): 

407 wafs = self.manager.get_resource_manager('wafv2').resources(augment=False) 

408 name_id_map = {w['Name']: w['ARN'] for w in wafs} 

409 state = self.data.get('state', True) 

410 

411 target_acl_id = '' 

412 if state: 

413 target_acl = self.data.get('web-acl', '') 

414 target_acl_ids = [v for k, v in name_id_map.items() if 

415 re.match(target_acl, k)] 

416 if len(target_acl_ids) != 1: 

417 raise ValueError(f'{target_acl} matching to none or ' 

418 f'multiple webacls') 

419 target_acl_id = target_acl_ids[0] 

420 

421 client = local_session( 

422 self.manager.session_factory).client('wafv2') 

423 

424 arn_key = self.manager.resource_type.id 

425 

426 # TODO implement force to reassociate. 

427 # TODO investigate limits on waf association. 

428 for r in resources: 

429 if state: 

430 self.retry(client.associate_web_acl, 

431 WebACLArn=target_acl_id, 

432 ResourceArn=r[arn_key]) 

433 else: 

434 self.retry(client.disassociate_web_acl, 

435 ResourceArn=r[arn_key]) 

436 

437 

438@AppELB.action_registry.register('set-s3-logging') 

439class SetS3Logging(BaseAction): 

440 """Action to enable/disable S3 logging for an application loadbalancer. 

441 

442 :example: 

443 

444 .. code-block:: yaml 

445 

446 policies: 

447 - name: elbv2-test 

448 resource: app-elb 

449 filters: 

450 - type: is-not-logging 

451 actions: 

452 - type: set-s3-logging 

453 bucket: elbv2logtest 

454 prefix: dahlogs 

455 state: enabled 

456 """ 

457 schema = type_schema( 

458 'set-s3-logging', 

459 state={'enum': ['enabled', 'disabled']}, 

460 bucket={'type': 'string'}, 

461 prefix={'type': 'string'}, 

462 required=('state',)) 

463 

464 permissions = ("elasticloadbalancing:ModifyLoadBalancerAttributes",) 

465 

466 def validate(self): 

467 if self.data.get('state') == 'enabled': 

468 if 'bucket' not in self.data or 'prefix' not in self.data: 

469 raise PolicyValidationError(( 

470 "alb logging enablement requires `bucket` " 

471 "and `prefix` specification on %s" % (self.manager.data,))) 

472 return self 

473 

474 def process(self, resources): 

475 client = local_session(self.manager.session_factory).client('elbv2') 

476 for elb in resources: 

477 elb_arn = elb['LoadBalancerArn'] 

478 attributes = [{ 

479 'Key': 'access_logs.s3.enabled', 

480 'Value': ( 

481 self.data.get('state') == 'enabled' and 'true' or 'value')}] 

482 

483 if self.data.get('state') == 'enabled': 

484 attributes.append({ 

485 'Key': 'access_logs.s3.bucket', 

486 'Value': self.data['bucket']}) 

487 

488 prefix_template = self.data['prefix'] 

489 info = {t['Key']: t['Value'] for t in elb.get('Tags', ())} 

490 info['DNSName'] = elb.get('DNSName', '') 

491 info['AccountId'] = elb['LoadBalancerArn'].split(':')[4] 

492 info['LoadBalancerName'] = elb['LoadBalancerName'] 

493 

494 attributes.append({ 

495 'Key': 'access_logs.s3.prefix', 

496 'Value': prefix_template.format(**info)}) 

497 

498 self.manager.retry( 

499 client.modify_load_balancer_attributes, 

500 LoadBalancerArn=elb_arn, Attributes=attributes) 

501 

502 

503@AppELB.action_registry.register('mark-for-op') 

504class AppELBMarkForOpAction(tags.TagDelayedAction): 

505 """Action to create a delayed action on an ELB to start at a later date 

506 

507 :example: 

508 

509 .. code-block:: yaml 

510 

511 policies: 

512 - name: appelb-failed-mark-for-op 

513 resource: app-elb 

514 filters: 

515 - "tag:custodian_elb_cleanup": absent 

516 - State: failed 

517 actions: 

518 - type: mark-for-op 

519 tag: custodian_elb_cleanup 

520 msg: "AppElb failed: {op}@{action_date}" 

521 op: delete 

522 days: 1 

523 """ 

524 

525 batch_size = 1 

526 

527 

528@AppELB.action_registry.register('tag') 

529class AppELBTagAction(tags.Tag): 

530 """Action to create tag/tags on an ELB 

531 

532 :example: 

533 

534 .. code-block:: yaml 

535 

536 policies: 

537 - name: appelb-create-required-tag 

538 resource: app-elb 

539 filters: 

540 - "tag:RequiredTag": absent 

541 actions: 

542 - type: tag 

543 key: RequiredTag 

544 value: RequiredValue 

545 """ 

546 

547 batch_size = 1 

548 permissions = ("elasticloadbalancing:AddTags",) 

549 

550 def process_resource_set(self, client, resource_set, ts): 

551 client.add_tags( 

552 ResourceArns=[alb['LoadBalancerArn'] for alb in resource_set], 

553 Tags=ts) 

554 

555 

556@AppELB.action_registry.register('remove-tag') 

557class AppELBRemoveTagAction(tags.RemoveTag): 

558 """Action to remove tag/tags from an ELB 

559 

560 :example: 

561 

562 .. code-block:: yaml 

563 

564 policies: 

565 - name: appelb-delete-expired-tag 

566 resource: app-elb 

567 filters: 

568 - "tag:ExpiredTag": present 

569 actions: 

570 - type: remove-tag 

571 tags: ["ExpiredTag"] 

572 """ 

573 

574 batch_size = 1 

575 permissions = ("elasticloadbalancing:RemoveTags",) 

576 

577 def process_resource_set(self, client, resource_set, tag_keys): 

578 client.remove_tags( 

579 ResourceArns=[alb['LoadBalancerArn'] for alb in resource_set], 

580 TagKeys=tag_keys) 

581 

582 

583@AppELB.action_registry.register('delete') 

584class AppELBDeleteAction(BaseAction): 

585 """Action to delete an ELB 

586 

587 To avoid unwanted deletions of ELB, it is recommended to apply a filter 

588 to the rule 

589 

590 :example: 

591 

592 .. code-block:: yaml 

593 

594 policies: 

595 - name: appelb-delete-failed-elb 

596 resource: app-elb 

597 filters: 

598 - State: failed 

599 actions: 

600 - delete 

601 """ 

602 

603 schema = type_schema('delete', force={'type': 'boolean'}) 

604 permissions = ( 

605 "elasticloadbalancing:DeleteLoadBalancer", 

606 "elasticloadbalancing:ModifyLoadBalancerAttributes",) 

607 

608 def process(self, load_balancers): 

609 client = local_session(self.manager.session_factory).client('elbv2') 

610 for lb in load_balancers: 

611 self.process_alb(client, lb) 

612 

613 def process_alb(self, client, alb): 

614 try: 

615 if self.data.get('force'): 

616 client.modify_load_balancer_attributes( 

617 LoadBalancerArn=alb['LoadBalancerArn'], 

618 Attributes=[{ 

619 'Key': 'deletion_protection.enabled', 

620 'Value': 'false', 

621 }]) 

622 self.manager.retry( 

623 client.delete_load_balancer, LoadBalancerArn=alb['LoadBalancerArn']) 

624 except client.exceptions.LoadBalancerNotFoundException: 

625 pass 

626 except ( 

627 client.exceptions.OperationNotPermittedException, 

628 client.exceptions.ResourceInUseException 

629 ) as e: 

630 self.log.warning( 

631 "Exception trying to delete load balancer: %s error: %s", 

632 alb['LoadBalancerArn'], e) 

633 

634 

635@AppELB.action_registry.register('modify-attributes') 

636class AppELBModifyAttributes(BaseAction): 

637 """Modify load balancer attributes. 

638 

639 :example: 

640 

641 .. code-block:: yaml 

642 

643 policies: 

644 - name: turn-on-elb-deletion-protection 

645 resource: app-elb 

646 filters: 

647 - type: attributes 

648 key: "deletion_protection.enabled" 

649 value: false 

650 actions: 

651 - type: modify-attributes 

652 attributes: 

653 "deletion_protection.enabled": "true" 

654 "idle_timeout.timeout_seconds": 120 

655 """ 

656 schema = { 

657 'type': 'object', 

658 'additionalProperties': False, 

659 'properties': { 

660 'type': { 

661 'enum': ['modify-attributes']}, 

662 'attributes': { 

663 'type': 'object', 

664 'additionalProperties': False, 

665 'properties': { 

666 'access_logs.s3.enabled': { 

667 'enum': ['true', 'false', True, False]}, 

668 'access_logs.s3.bucket': {'type': 'string'}, 

669 'access_logs.s3.prefix': {'type': 'string'}, 

670 'deletion_protection.enabled': { 

671 'enum': ['true', 'false', True, False]}, 

672 'idle_timeout.timeout_seconds': {'type': 'number'}, 

673 'routing.http.desync_mitigation_mode': { 

674 'enum': ['monitor', 'defensive', 'strictest']}, 

675 'routing.http.drop_invalid_header_fields.enabled': { 

676 'enum': ['true', 'false', True, False]}, 

677 'routing.http2.enabled': { 

678 'enum': ['true', 'false', True, False]}, 

679 'load_balancing.cross_zone.enabled': { 

680 'enum': ['true', 'false', True, False]}, 

681 }, 

682 }, 

683 }, 

684 } 

685 permissions = ("elasticloadbalancing:ModifyLoadBalancerAttributes",) 

686 

687 def process(self, resources): 

688 client = local_session(self.manager.session_factory).client('elbv2') 

689 for appelb in resources: 

690 self.manager.retry( 

691 client.modify_load_balancer_attributes, 

692 LoadBalancerArn=appelb['LoadBalancerArn'], 

693 Attributes=[ 

694 {'Key': key, 'Value': serialize_attribute_value(value)} 

695 for (key, value) in self.data['attributes'].items() 

696 ], 

697 ignore_err_codes=('LoadBalancerNotFoundException',), 

698 ) 

699 return resources 

700 

701 

702class AppELBListenerFilterBase: 

703 """ Mixin base class for filters that query LB listeners. 

704 """ 

705 permissions = ("elasticloadbalancing:DescribeListeners",) 

706 

707 def initialize(self, albs): 

708 client = local_session(self.manager.session_factory).client('elbv2') 

709 self.listener_map = defaultdict(list) 

710 for alb in albs: 

711 results = self.manager.retry(client.describe_listeners, 

712 LoadBalancerArn=alb['LoadBalancerArn'], 

713 ignore_err_codes=('LoadBalancerNotFoundException',)) 

714 self.listener_map[alb['LoadBalancerArn']] = results['Listeners'] 

715 

716 

717def parse_attribute_value(v): 

718 if v.isdigit(): 

719 v = int(v) 

720 elif v == 'true': 

721 v = True 

722 elif v == 'false': 

723 v = False 

724 return v 

725 

726 

727def serialize_attribute_value(v): 

728 if v is True: 

729 return 'true' 

730 elif v is False: 

731 return 'false' 

732 elif isinstance(v, int): 

733 return str(v) 

734 return v 

735 

736 

737class AppELBAttributeFilterBase: 

738 """ Mixin base class for filters that query LB attributes. 

739 """ 

740 

741 def initialize(self, albs): 

742 client = local_session(self.manager.session_factory).client('elbv2') 

743 

744 def _process_attributes(alb): 

745 if 'Attributes' not in alb: 

746 alb['Attributes'] = {} 

747 results = client.describe_load_balancer_attributes( 

748 LoadBalancerArn=alb['LoadBalancerArn']) 

749 # flatten out the list of dicts and cast 

750 for pair in results['Attributes']: 

751 k = pair['Key'] 

752 v = parse_attribute_value(pair['Value']) 

753 alb['Attributes'][k] = v 

754 

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

756 list(w.map(_process_attributes, albs)) 

757 

758 

759@AppELB.filter_registry.register('is-logging') 

760class IsLoggingFilter(Filter, AppELBAttributeFilterBase): 

761 """ Matches AppELBs that are logging to S3. 

762 bucket and prefix are optional 

763 

764 :example: 

765 

766 .. code-block:: yaml 

767 

768 policies: 

769 - name: alb-is-logging-test 

770 resource: app-elb 

771 filters: 

772 - type: is-logging 

773 

774 - name: alb-is-logging-bucket-and-prefix-test 

775 resource: app-elb 

776 filters: 

777 - type: is-logging 

778 bucket: prodlogs 

779 prefix: alblogs 

780 

781 """ 

782 permissions = ("elasticloadbalancing:DescribeLoadBalancerAttributes",) 

783 schema = type_schema('is-logging', 

784 bucket={'type': 'string'}, 

785 prefix={'type': 'string'} 

786 ) 

787 

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

789 self.initialize(resources) 

790 bucket_name = self.data.get('bucket', None) 

791 bucket_prefix = self.data.get('prefix', None) 

792 

793 return [alb for alb in resources 

794 if alb['Attributes']['access_logs.s3.enabled'] and 

795 (not bucket_name or bucket_name == alb['Attributes'].get( 

796 'access_logs.s3.bucket', None)) and 

797 (not bucket_prefix or bucket_prefix == alb['Attributes'].get( 

798 'access_logs.s3.prefix', None)) 

799 ] 

800 

801 

802@AppELB.filter_registry.register('is-not-logging') 

803class IsNotLoggingFilter(Filter, AppELBAttributeFilterBase): 

804 """ Matches AppELBs that are NOT logging to S3. 

805 or do not match the optional bucket and/or prefix. 

806 

807 :example: 

808 

809 .. code-block:: yaml 

810 

811 policies: 

812 - name: alb-is-not-logging-test 

813 resource: app-elb 

814 filters: 

815 - type: is-not-logging 

816 

817 - name: alb-is-not-logging-bucket-and-prefix-test 

818 resource: app-elb 

819 filters: 

820 - type: is-not-logging 

821 bucket: prodlogs 

822 prefix: alblogs 

823 

824 """ 

825 permissions = ("elasticloadbalancing:DescribeLoadBalancerAttributes",) 

826 schema = type_schema('is-not-logging', 

827 bucket={'type': 'string'}, 

828 prefix={'type': 'string'} 

829 ) 

830 

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

832 self.initialize(resources) 

833 bucket_name = self.data.get('bucket', None) 

834 bucket_prefix = self.data.get('prefix', None) 

835 

836 return [alb for alb in resources 

837 if not alb['Attributes']['access_logs.s3.enabled'] or 

838 (bucket_name and bucket_name != alb['Attributes'].get( 

839 'access_logs.s3.bucket', None)) or 

840 (bucket_prefix and bucket_prefix != alb['Attributes'].get( 

841 'access_logs.s3.prefix', None))] 

842 

843 

844@AppELB.filter_registry.register('attributes') 

845class CheckAttributes(ValueFilter, AppELBAttributeFilterBase): 

846 """ Value filter that allows filtering on ELBv2 attributes 

847 

848 :example: 

849 

850 .. code-block:: yaml 

851 

852 policies: 

853 - name: alb-http2-enabled 

854 resource: app-elb 

855 filters: 

856 - type: attributes 

857 key: routing.http2.enabled 

858 value: true 

859 op: eq 

860 """ 

861 annotate: False # no annotation from value Filter 

862 permissions = ("elasticloadbalancing:DescribeLoadBalancerAttributes",) 

863 schema = type_schema('attributes', rinherit=ValueFilter.schema) 

864 schema_alias = False 

865 

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

867 self.augment(resources) 

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

869 

870 def augment(self, resources): 

871 self.initialize(resources) 

872 

873 def __call__(self, r): 

874 return super().__call__(r['Attributes']) 

875 

876 

877class AppELBTargetGroupFilterBase: 

878 """ Mixin base class for filters that query LB target groups. 

879 """ 

880 

881 def initialize(self, albs): 

882 self.target_group_map = defaultdict(list) 

883 target_groups = self.manager.get_resource_manager( 

884 'app-elb-target-group').resources() 

885 for target_group in target_groups: 

886 for load_balancer_arn in target_group['LoadBalancerArns']: 

887 self.target_group_map[load_balancer_arn].append(target_group) 

888 

889 

890@AppELB.filter_registry.register('listener') 

891class AppELBListenerFilter(ValueFilter, AppELBListenerFilterBase): 

892 """Filter ALB based on matching listener attributes 

893 

894 Adding the `matched` flag will filter on previously matched listeners 

895 

896 :example: 

897 

898 .. code-block:: yaml 

899 

900 policies: 

901 - name: app-elb-invalid-ciphers 

902 resource: app-elb 

903 filters: 

904 - type: listener 

905 key: Protocol 

906 value: HTTPS 

907 - type: listener 

908 key: SslPolicy 

909 value: ['ELBSecurityPolicy-TLS-1-1-2017-01','ELBSecurityPolicy-TLS-1-2-2017-01'] 

910 op: ni 

911 matched: true 

912 actions: 

913 - type: modify-listener 

914 sslpolicy: "ELBSecurityPolicy-TLS-1-2-2017-01" 

915 """ 

916 

917 schema = type_schema( 

918 'listener', rinherit=ValueFilter.schema, matched={'type': 'boolean'}) 

919 schema_alias = False 

920 permissions = ("elasticloadbalancing:DescribeLoadBalancerAttributes",) 

921 

922 def validate(self): 

923 if not self.data.get('matched'): 

924 return 

925 listeners = list(self.manager.iter_filters()) 

926 found = False 

927 for f in listeners[:listeners.index(self)]: 

928 if not f.data.get('matched', False): 

929 found = True 

930 break 

931 if not found: 

932 raise PolicyValidationError( 

933 "matched listener filter, requires preceding listener filter on %s " % ( 

934 self.manager.data,)) 

935 return self 

936 

937 def process(self, albs, event=None): 

938 self.initialize(albs) 

939 return super(AppELBListenerFilter, self).process(albs, event) 

940 

941 def __call__(self, alb): 

942 listeners = self.listener_map[alb['LoadBalancerArn']] 

943 if self.data.get('matched', False): 

944 listeners = alb.pop('c7n:MatchedListeners', []) 

945 

946 found_listeners = False 

947 for listener in listeners: 

948 if self.match(listener): 

949 set_annotation(alb, 'c7n:MatchedListeners', listener) 

950 found_listeners = True 

951 return found_listeners 

952 

953 

954@AppELB.filter_registry.register('listener-rule') 

955class AppELBListenerRuleFilter(ListItemFilter): 

956 """Filter ALB based on listener rules (path-based, host-based routing, etc.) 

957 

958 This filter allows checking multiple attributes on listener rules, 

959 enabling policies to inspect non-default routing configurations 

960 for security and compliance. 

961 

962 :example: 

963 

964 Find ALBs with rules redirecting to HTTP (insecure): 

965 

966 .. code-block:: yaml 

967 

968 policies: 

969 - name: alb-insecure-rule-redirects 

970 resource: app-elb 

971 filters: 

972 - type: listener-rule 

973 attrs: 

974 - type: value 

975 key: Actions[0].Type 

976 value: redirect 

977 - type: value 

978 key: Actions[0].RedirectConfig.Protocol 

979 value: HTTP 

980 

981 Find ALBs with rules forwarding to specific target groups: 

982 

983 .. code-block:: yaml 

984 

985 policies: 

986 - name: alb-rules-to-old-target-group 

987 resource: app-elb 

988 filters: 

989 - type: listener-rule 

990 attrs: 

991 - type: value 

992 key: Actions[0].TargetGroupArn 

993 value: "arn:aws:elasticloadbalancing:*:*:targetgroup/old-*" 

994 op: glob 

995 

996 Count ALBs with more than 5 custom rules: 

997 

998 .. code-block:: yaml 

999 

1000 policies: 

1001 - name: alb-many-rules 

1002 resource: app-elb 

1003 filters: 

1004 - type: listener-rule 

1005 count: 5 

1006 count_op: gt 

1007 """ 

1008 schema = type_schema( 

1009 'listener-rule', 

1010 attrs={'$ref': '#/definitions/filters_common/list_item_attrs'}, 

1011 count={'type': 'number'}, 

1012 count_op={'$ref': '#/definitions/filters_common/comparison_operators'} 

1013 ) 

1014 permissions = ( 

1015 'elasticloadbalancing:DescribeListeners', 

1016 'elasticloadbalancing:DescribeRules', 

1017 ) 

1018 annotate_items = True 

1019 item_annotation_key = 'c7n:ListenerRules' 

1020 

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

1022 client = local_session(self.manager.session_factory).client('elbv2') 

1023 rule_map = defaultdict(list) 

1024 

1025 # Fetch all listeners and their rules for the ALBs 

1026 for alb in resources: 

1027 try: 

1028 listeners_result = self.manager.retry( 

1029 client.describe_listeners, 

1030 LoadBalancerArn=alb['LoadBalancerArn'], 

1031 ignore_err_codes=('LoadBalancerNotFoundException',) 

1032 ) 

1033 listeners = listeners_result.get('Listeners', []) 

1034 

1035 # Fetch rules for each listener 

1036 for listener in listeners: 

1037 try: 

1038 rules_result = self.manager.retry( 

1039 client.describe_rules, 

1040 ListenerArn=listener['ListenerArn'], 

1041 ignore_err_codes=('ListenerNotFoundException',) 

1042 ) 

1043 rules = rules_result.get('Rules', []) 

1044 # Filter out default rules (IsDefault=True) 

1045 # as they're already covered by the listener filter 

1046 non_default_rules = [ 

1047 r for r in rules if not r.get('IsDefault', False) 

1048 ] 

1049 rule_map[alb['LoadBalancerArn']].extend(non_default_rules) 

1050 except ClientError as e: 

1051 log.warning( 

1052 "Failed to fetch rules for listener %s: %s", 

1053 listener.get('ListenerArn'), e 

1054 ) 

1055 except ClientError as e: 

1056 log.warning( 

1057 "Failed to fetch listeners for ALB %s: %s", 

1058 alb.get('LoadBalancerArn'), e 

1059 ) 

1060 

1061 # Store rules in the ALB resources 

1062 for alb in resources: 

1063 alb[self.item_annotation_key] = rule_map.get( 

1064 alb['LoadBalancerArn'], [] 

1065 ) 

1066 

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

1068 

1069 def get_item_values(self, resource): 

1070 return resource.get(self.item_annotation_key, []) 

1071 

1072 

1073@AppELB.action_registry.register('modify-listener') 

1074class AppELBModifyListenerPolicy(BaseAction): 

1075 """Action to modify the policy for an App ELB 

1076 

1077 :example: 

1078 

1079 .. code-block:: yaml 

1080 

1081 policies: 

1082 - name: appelb-modify-listener 

1083 resource: app-elb 

1084 filters: 

1085 - type: listener 

1086 key: Protocol 

1087 value: HTTP 

1088 actions: 

1089 - type: modify-listener 

1090 protocol: HTTPS 

1091 sslpolicy: "ELBSecurityPolicy-TLS-1-2-2017-01" 

1092 certificate: "arn:aws:acm:region:123456789012:certificate/12345678-\ 

1093 1234-1234-1234-123456789012" 

1094 """ 

1095 

1096 schema = type_schema( 

1097 'modify-listener', 

1098 port={'type': 'integer'}, 

1099 protocol={'enum': ['HTTP', 'HTTPS', 'TCP', 'TLS', 'UDP', 'TCP_UDP', 'GENEVE']}, 

1100 sslpolicy={'type': 'string'}, 

1101 certificate={'type': 'string'} 

1102 ) 

1103 

1104 permissions = ("elasticloadbalancing:ModifyListener",) 

1105 

1106 def validate(self): 

1107 for f in self.manager.iter_filters(): 

1108 if f.type == 'listener': 

1109 return self 

1110 raise PolicyValidationError( 

1111 "modify-listener action requires the listener filter %s" % ( 

1112 self.manager.data,)) 

1113 

1114 def process(self, load_balancers): 

1115 args = {} 

1116 if 'port' in self.data: 

1117 args['Port'] = self.data.get('port') 

1118 if 'protocol' in self.data: 

1119 args['Protocol'] = self.data.get('protocol') 

1120 if 'sslpolicy' in self.data: 

1121 args['SslPolicy'] = self.data.get('sslpolicy') 

1122 if 'certificate' in self.data: 

1123 args['Certificates'] = [{'CertificateArn': self.data.get('certificate')}] 

1124 client = local_session(self.manager.session_factory).client('elbv2') 

1125 

1126 for alb in load_balancers: 

1127 for matched_listener in alb.get('c7n:MatchedListeners', ()): 

1128 client.modify_listener( 

1129 ListenerArn=matched_listener['ListenerArn'], 

1130 **args) 

1131 

1132 

1133@AppELB.action_registry.register('modify-security-groups') 

1134class AppELBModifyVpcSecurityGroups(ModifyVpcSecurityGroupsAction): 

1135 

1136 permissions = ("elasticloadbalancing:SetSecurityGroups",) 

1137 

1138 def process(self, albs): 

1139 client = local_session(self.manager.session_factory).client('elbv2') 

1140 groups = super(AppELBModifyVpcSecurityGroups, self).get_groups(albs) 

1141 

1142 for idx, i in enumerate(albs): 

1143 try: 

1144 client.set_security_groups( 

1145 LoadBalancerArn=i['LoadBalancerArn'], 

1146 SecurityGroups=groups[idx]) 

1147 except client.exceptions.LoadBalancerNotFoundException: 

1148 continue 

1149 

1150 

1151@AppELB.filter_registry.register('healthcheck-protocol-mismatch') 

1152class AppELBHealthCheckProtocolMismatchFilter(Filter, 

1153 AppELBTargetGroupFilterBase): 

1154 """Filter AppELBs with mismatched health check protocols 

1155 

1156 A mismatched health check protocol is where the protocol on the target group 

1157 does not match the load balancer health check protocol 

1158 

1159 :example: 

1160 

1161 .. code-block:: yaml 

1162 

1163 policies: 

1164 - name: appelb-healthcheck-mismatch 

1165 resource: app-elb 

1166 filters: 

1167 - healthcheck-protocol-mismatch 

1168 """ 

1169 

1170 schema = type_schema('healthcheck-protocol-mismatch') 

1171 permissions = ("elasticloadbalancing:DescribeTargetGroups",) 

1172 

1173 def process(self, albs, event=None): 

1174 def _healthcheck_protocol_mismatch(alb): 

1175 for target_group in self.target_group_map[alb['LoadBalancerArn']]: 

1176 if (target_group['Protocol'] != 

1177 target_group['HealthCheckProtocol']): 

1178 return True 

1179 

1180 return False 

1181 

1182 self.initialize(albs) 

1183 return [alb for alb in albs if _healthcheck_protocol_mismatch(alb)] 

1184 

1185 

1186@AppELB.filter_registry.register('target-group') 

1187class AppELBTargetGroupFilter(ValueFilter, AppELBTargetGroupFilterBase): 

1188 """Filter ALB based on matching target group value""" 

1189 

1190 schema = type_schema('target-group', rinherit=ValueFilter.schema) 

1191 schema_alias = False 

1192 permissions = ("elasticloadbalancing:DescribeTargetGroups",) 

1193 

1194 def process(self, albs, event=None): 

1195 self.initialize(albs) 

1196 return super(AppELBTargetGroupFilter, self).process(albs, event) 

1197 

1198 def __call__(self, alb): 

1199 target_groups = self.target_group_map[alb['LoadBalancerArn']] 

1200 return self.match(target_groups) 

1201 

1202 

1203@AppELB.filter_registry.register('default-vpc') 

1204class AppELBDefaultVpcFilter(net_filters.DefaultVpcBase): 

1205 """Filter all ELB that exist within the default vpc 

1206 

1207 :example: 

1208 

1209 .. code-block:: yaml 

1210 

1211 policies: 

1212 - name: appelb-in-default-vpc 

1213 resource: app-elb 

1214 filters: 

1215 - default-vpc 

1216 """ 

1217 

1218 schema = type_schema('default-vpc') 

1219 

1220 def __call__(self, alb): 

1221 return alb.get('VpcId') and self.match(alb.get('VpcId')) or False 

1222 

1223 

1224class DescribeAppELBTargetGroup(DescribeSource): 

1225 

1226 def augment(self, target_groups): 

1227 client = local_session(self.manager.session_factory).client('elbv2') 

1228 

1229 def _describe_target_group_health(target_group): 

1230 result = self.manager.retry(client.describe_target_health, 

1231 TargetGroupArn=target_group['TargetGroupArn']) 

1232 target_group['TargetHealthDescriptions'] = result[ 

1233 'TargetHealthDescriptions'] 

1234 

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

1236 list(w.map(_describe_target_group_health, target_groups)) 

1237 

1238 _describe_target_group_tags( 

1239 target_groups, self.manager.session_factory, 

1240 self.manager.executor_factory, self.manager.retry) 

1241 return target_groups 

1242 

1243 

1244@resources.register('app-elb-target-group') 

1245class AppELBTargetGroup(QueryResourceManager): 

1246 """Resource manager for v2 ELB target groups. 

1247 """ 

1248 

1249 class resource_type(TypeInfo): 

1250 service = 'elbv2' 

1251 arn_type = 'target-group' 

1252 enum_spec = ('describe_target_groups', 'TargetGroups', None) 

1253 name = 'TargetGroupName' 

1254 id = 'TargetGroupArn' 

1255 permission_prefix = 'elasticloadbalancing' 

1256 cfn_type = config_type = 'AWS::ElasticLoadBalancingV2::TargetGroup' 

1257 

1258 source_mapping = { 

1259 'describe': DescribeAppELBTargetGroup, 

1260 'config': ConfigSource, 

1261 } 

1262 

1263 filter_registry = FilterRegistry('app-elb-target-group.filters') 

1264 action_registry = ActionRegistry('app-elb-target-group.actions') 

1265 retry = staticmethod(get_retry(('Throttling',))) 

1266 

1267 filter_registry.register('tag-count', tags.TagCountFilter) 

1268 filter_registry.register('marked-for-op', tags.TagActionFilter) 

1269 

1270 @classmethod 

1271 def get_permissions(cls): 

1272 # override as the service is not the iam prefix 

1273 return ("elasticloadbalancing:DescribeTargetGroups", 

1274 "elasticloadbalancing:DescribeTags") 

1275 

1276 

1277def _describe_target_group_tags(target_groups, session_factory, 

1278 executor_factory, retry): 

1279 client = local_session(session_factory).client('elbv2') 

1280 

1281 def _process_tags(target_group_set): 

1282 target_group_map = { 

1283 target_group['TargetGroupArn']: 

1284 target_group for target_group in target_group_set 

1285 } 

1286 

1287 results = retry( 

1288 client.describe_tags, 

1289 ResourceArns=list(target_group_map.keys())) 

1290 for tag_desc in results['TagDescriptions']: 

1291 if ('ResourceArn' in tag_desc and 

1292 tag_desc['ResourceArn'] in target_group_map): 

1293 target_group_map[ 

1294 tag_desc['ResourceArn'] 

1295 ]['Tags'] = tag_desc['Tags'] 

1296 

1297 with executor_factory(max_workers=2) as w: 

1298 list(w.map(_process_tags, chunks(target_groups, 20))) 

1299 

1300 

1301@AppELBTargetGroup.action_registry.register('mark-for-op') 

1302class AppELBTargetGroupMarkForOpAction(tags.TagDelayedAction): 

1303 """Action to specify a delayed action on an ELB target group""" 

1304 

1305 

1306@AppELBTargetGroup.action_registry.register('tag') 

1307class AppELBTargetGroupTagAction(tags.Tag): 

1308 """Action to create tag/tags on an ELB target group 

1309 

1310 :example: 

1311 

1312 .. code-block:: yaml 

1313 

1314 policies: 

1315 - name: appelb-targetgroup-add-required-tag 

1316 resource: app-elb-target-group 

1317 filters: 

1318 - "tag:RequiredTag": absent 

1319 actions: 

1320 - type: tag 

1321 key: RequiredTag 

1322 value: RequiredValue 

1323 """ 

1324 

1325 batch_size = 1 

1326 permissions = ("elasticloadbalancing:AddTags",) 

1327 

1328 def process_resource_set(self, client, resource_set, ts): 

1329 client.add_tags( 

1330 ResourceArns=[tgroup['TargetGroupArn'] for tgroup in resource_set], 

1331 Tags=ts) 

1332 

1333 

1334@AppELBTargetGroup.action_registry.register('remove-tag') 

1335class AppELBTargetGroupRemoveTagAction(tags.RemoveTag): 

1336 """Action to remove tag/tags from ELB target group 

1337 

1338 :example: 

1339 

1340 .. code-block:: yaml 

1341 

1342 policies: 

1343 - name: appelb-targetgroup-remove-expired-tag 

1344 resource: app-elb-target-group 

1345 filters: 

1346 - "tag:ExpiredTag": present 

1347 actions: 

1348 - type: remove-tag 

1349 tags: ["ExpiredTag"] 

1350 """ 

1351 

1352 batch_size = 1 

1353 permissions = ("elasticloadbalancing:RemoveTags",) 

1354 

1355 def process_resource_set(self, client, resource_set, tag_keys): 

1356 client.remove_tags( 

1357 ResourceArns=[tgroup['TargetGroupArn'] for tgroup in resource_set], 

1358 TagKeys=tag_keys) 

1359 

1360 

1361@AppELBTargetGroup.filter_registry.register('default-vpc') 

1362class AppELBTargetGroupDefaultVpcFilter(net_filters.DefaultVpcBase): 

1363 """Filter all application elb target groups within the default vpc 

1364 

1365 :example: 

1366 

1367 .. code-block:: yaml 

1368 

1369 policies: 

1370 - name: appelb-targetgroups-default-vpc 

1371 resource: app-elb-target-group 

1372 filters: 

1373 - default-vpc 

1374 """ 

1375 

1376 schema = type_schema('default-vpc') 

1377 

1378 def __call__(self, target_group): 

1379 return (target_group.get('VpcId') and 

1380 self.match(target_group.get('VpcId')) or False) 

1381 

1382 

1383@AppELBTargetGroup.action_registry.register('delete') 

1384class AppELBTargetGroupDeleteAction(BaseAction): 

1385 """Action to delete ELB target group 

1386 

1387 It is recommended to apply a filter to the delete policy to avoid unwanted 

1388 deletion of any app elb target groups. 

1389 

1390 :example: 

1391 

1392 .. code-block:: yaml 

1393 

1394 policies: 

1395 - name: appelb-targetgroups-delete-unused 

1396 resource: app-elb-target-group 

1397 filters: 

1398 - "tag:SomeTag": absent 

1399 actions: 

1400 - delete 

1401 """ 

1402 

1403 schema = type_schema('delete') 

1404 permissions = ('elasticloadbalancing:DeleteTargetGroup',) 

1405 

1406 def process(self, resources): 

1407 client = local_session(self.manager.session_factory).client('elbv2') 

1408 for tg in resources: 

1409 self.process_target_group(client, tg) 

1410 

1411 def process_target_group(self, client, target_group): 

1412 self.manager.retry( 

1413 client.delete_target_group, 

1414 TargetGroupArn=target_group['TargetGroupArn']) 

1415 

1416 

1417class TargetGroupAttributeFilterBase: 

1418 """ Mixin base class for filters that query Target Group attributes. 

1419 """ 

1420 

1421 def initialize(self, tgs): 

1422 client = local_session(self.manager.session_factory).client('elbv2') 

1423 

1424 def _process_attributes(tg): 

1425 if 'c7n:TargetGroupAttributes' not in tg: 

1426 tg['c7n:TargetGroupAttributes'] = {} 

1427 results = self.manager.retry(client.describe_target_group_attributes, 

1428 TargetGroupArn=tg['TargetGroupArn'], 

1429 ignore_err_codes=('TargetGroupNotFoundException',)) 

1430 # flatten out the list of dicts and cast 

1431 for pair in results['Attributes']: 

1432 k = pair['Key'] 

1433 v = parse_attribute_value(pair['Value']) 

1434 tg['c7n:TargetGroupAttributes'][k] = v 

1435 

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

1437 list(w.map(_process_attributes, tgs)) 

1438 

1439 

1440@AppELBTargetGroup.filter_registry.register('attributes') 

1441class TargetGroupCheckAttributes(ValueFilter, TargetGroupAttributeFilterBase): 

1442 """ Value filter that allows filtering on Target group attributes 

1443 

1444 :example: 

1445 

1446 .. code-block:: yaml 

1447 

1448 policies: 

1449 - name: target-group-check-attributes 

1450 resource: app-elb-target-group 

1451 filters: 

1452 - type: attributes 

1453 key: preserve_client_ip.enabled 

1454 value: True 

1455 op: eq 

1456 """ 

1457 annotate: False # no annotation from value Filter 

1458 permissions = ("elasticloadbalancing:DescribeTargetGroupAttributes",) 

1459 schema = type_schema('attributes', rinherit=ValueFilter.schema) 

1460 schema_alias = False 

1461 

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

1463 self.augment(resources) 

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

1465 

1466 def augment(self, resources): 

1467 self.initialize(resources) 

1468 

1469 def __call__(self, r): 

1470 return super().__call__(r['c7n:TargetGroupAttributes']) 

1471 

1472 

1473@AppELBTargetGroup.action_registry.register('modify-attributes') 

1474class AppELBTargetGroupModifyAttributes(BaseAction): 

1475 """Modify target group attributes. 

1476 

1477 :example: 

1478 

1479 .. code-block:: yaml 

1480 

1481 policies: 

1482 - name: modify-preserve-client-ip-enable 

1483 resource: app-elb-target-group 

1484 filters: 

1485 - type: attributes 

1486 key: "preserve_client_ip.enabled" 

1487 value: False 

1488 actions: 

1489 - type: modify-attributes 

1490 attributes: 

1491 "preserve_client_ip.enabled": "true" 

1492 """ 

1493 schema = { 

1494 'type': 'object', 

1495 'additionalProperties': False, 

1496 'properties': { 

1497 'type': { 

1498 'enum': ['modify-attributes']}, 

1499 'attributes': { 

1500 'type': 'object', 

1501 'additionalProperties': False, 

1502 'properties': { 

1503 'proxy_protocol_v2.enabled': { 

1504 'enum': ['true', 'false', True, False]}, 

1505 'preserve_client_ip.enabled': { 

1506 'enum': ['true', 'false', True, False]}, 

1507 'stickiness.enabled': { 

1508 'enum': ['true', 'false', True, False]}, 

1509 'lambda.multi_value_headers.enabled': { 

1510 'enum': ['true', 'false', True, False]}, 

1511 'deregistration_delay.connection_termination.enabled': { 

1512 'enum': ['true', 'false', True, False]}, 

1513 'target_group_health.unhealthy_state_routing.' 

1514 'minimum_healthy_targets.count': {'type': 'number'}, 

1515 'target_group_health.unhealthy_state_routing.' 

1516 'minimum_healthy_targets.percentage': {'type': 'string'}, 

1517 'deregistration_delay.timeout_seconds': {'type': 'number'}, 

1518 'target_group_health.dns_failover.minimum_healthy_targets.count': { 

1519 'type': 'string'}, 

1520 'stickiness.type': { 

1521 'enum': ['lb_cookie', 'app_cookie', 'source_ip', 

1522 'source_ip_dest_ip', 'source_ip_dest_ip_proto']}, 

1523 'load_balancing.cross_zone.enabled': { 

1524 'enum': ['true', 'false', True, False, 'use_load_balancer_configuration']}, 

1525 'target_group_health.dns_failover.minimum_healthy_targets.percentage': { 

1526 'type': 'string'}, 

1527 'stickiness.app_cookie.cookie_name': {'type': 'string'}, 

1528 'stickiness.lb_cookie.duration_seconds': {'type': 'number'}, 

1529 'slow_start.duration_seconds': {'type': 'number'}, 

1530 'stickiness.app_cookie.duration_seconds': {'type': 'number'}, 

1531 'load_balancing.algorithm.type': { 

1532 'enum': ['round_robin', 'least_outstanding_requests']}, 

1533 'target_failover.on_deregistration': { 

1534 'enum': ['rebalance', 'no_rebalance']}, 

1535 'target_failover.on_unhealthy': { 

1536 'enum': ['rebalance', 'no_rebalance']}, 

1537 }, 

1538 }, 

1539 }, 

1540 } 

1541 permissions = ("elasticloadbalancing:ModifyTargetGroupAttributes",) 

1542 

1543 def process(self, resources): 

1544 client = local_session(self.manager.session_factory).client('elbv2') 

1545 self.log.info(resources) 

1546 for targetgroup in resources: 

1547 self.manager.retry( 

1548 client.modify_target_group_attributes, 

1549 TargetGroupArn=targetgroup['TargetGroupArn'], 

1550 Attributes=[ 

1551 {'Key': key, 'Value': serialize_attribute_value(value)} 

1552 for (key, value) in self.data['attributes'].items() 

1553 ], 

1554 ignore_err_codes=('TargetGroupNotFoundException',), 

1555 ) 

1556 return resources 

1557 

1558 

1559@AppELB.action_registry.register('delete-listener') 

1560class AppELBDeleteListenerAction(BaseAction): 

1561 """Action to delete listeners from an Application Load Balancer. 

1562 

1563 

1564 :example: 

1565 

1566 .. code-block:: yaml 

1567 

1568 policies: 

1569 - name: delete-alb-listeners 

1570 resource: app-elb 

1571 filters: 

1572 - type: listener 

1573 key: Protocol 

1574 value: HTTP 

1575 actions: 

1576 - type: delete-listener 

1577 scope: matched 

1578 """ 

1579 

1580 def validate(self): 

1581 """Validate the delete-listener action configuration. 

1582 

1583 The listener filter is only required when the action is scoped to 

1584 the *matched* listeners. 

1585 """ 

1586 scope = self.data.get('scope', 'matched') 

1587 if scope == 'matched': 

1588 for f in self.manager.iter_filters(): 

1589 if f.type == 'listener': 

1590 return self 

1591 raise PolicyValidationError( 

1592 "delete-listener action with scope 'matched' requires the listener filter %s" % 

1593 (self.manager.data,)) 

1594 

1595 schema = type_schema( 

1596 'delete-listener', 

1597 scope={'enum': ['matched']}) 

1598 permissions = ("elasticloadbalancing:DeleteListener",) 

1599 

1600 def process(self, albs): 

1601 client = local_session(self.manager.session_factory).client('elbv2') 

1602 for alb in albs: 

1603 listeners = alb.get('c7n:MatchedListeners') 

1604 for listener in listeners: 

1605 try: 

1606 client.delete_listener( 

1607 ListenerArn=listener['ListenerArn']) 

1608 except client.exceptions.ListenerNotFoundException: 

1609 continue