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

480 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 

3""" 

4Application & Network Load Balancers 

5""" 

6import json 

7import logging 

8import re 

9 

10from collections import defaultdict 

11from c7n.actions import ActionRegistry, BaseAction, ModifyVpcSecurityGroupsAction 

12from c7n.exceptions import PolicyValidationError 

13from c7n.filters import ( 

14 Filter, 

15 FilterRegistry, 

16 MetricsFilter, 

17 ValueFilter, 

18 WafV2FilterBase, 

19 WafClassicRegionalFilterBase 

20) 

21import c7n.filters.vpc as net_filters 

22from c7n import tags 

23from c7n.manager import resources 

24 

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

26from c7n.utils import ( 

27 local_session, chunks, type_schema, get_retry, set_annotation) 

28 

29from c7n.resources.aws import Arn 

30from c7n.resources.shield import IsShieldProtected, SetShieldProtection 

31 

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

33 

34 

35class DescribeAppElb(DescribeSource): 

36 

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

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

39 """ 

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

41 params = {'LoadBalancerArns': ids} 

42 else: 

43 params = {'Names': ids} 

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

45 

46 def augment(self, albs): 

47 _describe_appelb_tags( 

48 albs, 

49 self.manager.session_factory, 

50 self.manager.executor_factory, 

51 self.manager.retry) 

52 

53 return albs 

54 

55 

56class ConfigAppElb(ConfigSource): 

57 

58 def load_resource(self, item): 

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

60 item_attrs = item['supplementaryConfiguration'][ 

61 'LoadBalancerAttributes'] 

62 if isinstance(item_attrs, str): 

63 item_attrs = json.loads(item_attrs) 

64 # Matches annotation of AppELBAttributeFilterBase filter 

65 resource['Attributes'] = { 

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

67 attr in item_attrs} 

68 return resource 

69 

70 

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

72class AppELB(QueryResourceManager): 

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

74 """ 

75 

76 class resource_type(TypeInfo): 

77 service = 'elbv2' 

78 permission_prefix = 'elasticloadbalancing' 

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

80 name = 'LoadBalancerName' 

81 id = 'LoadBalancerArn' 

82 filter_name = "Names" 

83 filter_type = "list" 

84 dimension = "LoadBalancer" 

85 date = 'CreatedTime' 

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

87 arn = "LoadBalancerArn" 

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

89 arn_type = 'loadbalancer/app' 

90 

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

92 source_mapping = { 

93 'describe': DescribeAppElb, 

94 'config': ConfigAppElb 

95 } 

96 

97 @classmethod 

98 def get_permissions(cls): 

99 # override as the service is not the iam prefix 

100 return ("elasticloadbalancing:DescribeLoadBalancers", 

101 "elasticloadbalancing:DescribeLoadBalancerAttributes", 

102 "elasticloadbalancing:DescribeTags") 

103 

104 

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

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

107 

108 def _process_tags(alb_set): 

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

110 

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

112 for tag_desc in results['TagDescriptions']: 

113 if ('ResourceArn' in tag_desc and 

114 tag_desc['ResourceArn'] in alb_map): 

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

116 

117 with executor_factory(max_workers=2) as w: 

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

119 

120 

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

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

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

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

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

126 

127 

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

129class AppElbMetrics(MetricsFilter): 

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

131 

132 Note application and network load balancers use different Cloud 

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

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

135 filter should be used to ensure only targeting a particular 

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

137 

138 See available application load balancer metrics here 

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

140 

141 See available network load balancer metrics here. 

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

143 

144 

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

146 the namespace parameter to the filter. 

147 

148 .. code-block:: yaml 

149 

150 policies: 

151 - name: net-lb-underutilized 

152 resource: app-elb 

153 filters: 

154 - Type: network 

155 - type: metrics 

156 name: ActiveFlowCount 

157 namespace: AWS/NetworkELB 

158 statistics: Sum 

159 days: 14 

160 value: 100 

161 op: less-than 

162 """ 

163 

164 def get_dimensions(self, resource): 

165 return [{ 

166 'Name': self.model.dimension, 

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

168 

169 

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

171class SecurityGroupFilter(net_filters.SecurityGroupFilter): 

172 

173 RelatedIdsExpression = "SecurityGroups[]" 

174 

175 

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

177class SubnetFilter(net_filters.SubnetFilter): 

178 

179 RelatedIdsExpression = "AvailabilityZones[].SubnetId" 

180 

181 

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

183class VpcFilter(net_filters.VpcFilter): 

184 

185 RelatedIdsExpression = "VpcId" 

186 

187 

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

189class WafEnabled(WafClassicRegionalFilterBase): 

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

191 

192 :example: 

193 

194 .. code-block:: yaml 

195 

196 policies: 

197 - name: filter-elb-waf-regional 

198 resource: app-elb 

199 filters: 

200 - type: waf-enabled 

201 state: false 

202 web-acl: test 

203 """ 

204 

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

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

207 def get_associated_web_acl(self, resource): 

208 return self.get_web_acl_from_associations( 

209 'APPLICATION_LOAD_BALANCER', 

210 resource['LoadBalancerArn'] 

211 ) 

212 

213 

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

215class WafV2Enabled(WafV2FilterBase): 

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

217 

218 Supports regex expression for web-acl. 

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

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

221 

222 :example: 

223 

224 .. code-block:: yaml 

225 

226 policies: 

227 - name: filter-wafv2-elb 

228 resource: app-elb 

229 filters: 

230 - type: wafv2-enabled 

231 state: false 

232 web-acl: testv2 

233 

234 - name: filter-wafv2-elb-regex 

235 resource: app-elb 

236 filters: 

237 - type: wafv2-enabled 

238 state: false 

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

240 """ 

241 

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

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

244 def get_associated_web_acl(self, resource): 

245 return self.get_web_acl_from_associations( 

246 'APPLICATION_LOAD_BALANCER', 

247 resource['LoadBalancerArn'] 

248 ) 

249 

250 

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

252class SetWaf(BaseAction): 

253 """Enable wafv2 protection on Application LoadBalancer. 

254 

255 :example: 

256 

257 .. code-block:: yaml 

258 

259 policies: 

260 - name: set-waf-for-elb 

261 resource: app-elb 

262 filters: 

263 - type: waf-enabled 

264 state: false 

265 web-acl: test 

266 actions: 

267 - type: set-waf 

268 state: true 

269 web-acl: test 

270 

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

272 resource: app-elb 

273 filters: 

274 - type: wafv2-enabled 

275 state: true 

276 actions: 

277 - type: set-waf 

278 state: true 

279 web-acl: test 

280 

281 """ 

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

283 

284 schema = type_schema( 

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

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

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

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

289 

290 def validate(self): 

291 found = False 

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

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

294 found = True 

295 break 

296 if not found: 

297 # try to ensure idempotent usage 

298 raise PolicyValidationError( 

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

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

301 return self 

302 

303 def process(self, resources): 

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

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

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

307 target_acl_id = name_id_map.get(target_acl, target_acl) 

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

309 

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

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

312 

313 client = local_session( 

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

315 

316 arn_key = self.manager.resource_type.id 

317 

318 # TODO implement force to reassociate. 

319 # TODO investigate limits on waf association. 

320 for r in resources: 

321 if state: 

322 client.associate_web_acl( 

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

324 else: 

325 client.disassociate_web_acl( 

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

327 

328 

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

330class SetWafV2(BaseAction): 

331 """Enable wafv2 protection on Application LoadBalancer. 

332 

333 Supports regex expression for web-acl 

334 

335 :example: 

336 

337 .. code-block:: yaml 

338 

339 policies: 

340 - name: set-wafv2-for-elb 

341 resource: app-elb 

342 filters: 

343 - type: wafv2-enabled 

344 state: false 

345 web-acl: testv2 

346 actions: 

347 - type: set-wafv2 

348 state: true 

349 web-acl: testv2 

350 

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

352 resource: app-elb 

353 filters: 

354 - type: waf-enabled 

355 state: true 

356 actions: 

357 - type: set-wafv2 

358 state: true 

359 

360 policies: 

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

362 resource: app-elb 

363 filters: 

364 - type: wafv2-enabled 

365 state: false 

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

367 actions: 

368 - type: set-wafv2 

369 state: true 

370 web-acl: FMManagedWebACLV2-?FMS-TestWebACL 

371 

372 """ 

373 permissions = ('wafv2:AssociateWebACL', 

374 'wafv2:DisassociateWebACL', 

375 'wafv2:ListWebACLs') 

376 

377 schema = type_schema( 

378 'set-wafv2', **{ 

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

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

381 

382 retry = staticmethod(get_retry(( 

383 'ThrottlingException', 

384 'RequestLimitExceeded', 

385 'Throttled', 

386 'ThrottledException', 

387 'Throttling', 

388 'Client.RequestLimitExceeded'))) 

389 

390 def validate(self): 

391 found = False 

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

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

394 found = True 

395 break 

396 if not found: 

397 # try to ensure idempotent usage 

398 raise PolicyValidationError( 

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

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

401 return self 

402 

403 def process(self, resources): 

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

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

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

407 

408 target_acl_id = '' 

409 if state: 

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

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

412 re.match(target_acl, k)] 

413 if len(target_acl_ids) != 1: 

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

415 f'multiple webacls') 

416 target_acl_id = target_acl_ids[0] 

417 

418 client = local_session( 

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

420 

421 arn_key = self.manager.resource_type.id 

422 

423 # TODO implement force to reassociate. 

424 # TODO investigate limits on waf association. 

425 for r in resources: 

426 if state: 

427 self.retry(client.associate_web_acl, 

428 WebACLArn=target_acl_id, 

429 ResourceArn=r[arn_key]) 

430 else: 

431 self.retry(client.disassociate_web_acl, 

432 ResourceArn=r[arn_key]) 

433 

434 

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

436class SetS3Logging(BaseAction): 

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

438 

439 :example: 

440 

441 .. code-block:: yaml 

442 

443 policies: 

444 - name: elbv2-test 

445 resource: app-elb 

446 filters: 

447 - type: is-not-logging 

448 actions: 

449 - type: set-s3-logging 

450 bucket: elbv2logtest 

451 prefix: dahlogs 

452 state: enabled 

453 """ 

454 schema = type_schema( 

455 'set-s3-logging', 

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

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

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

459 required=('state',)) 

460 

461 permissions = ("elasticloadbalancing:ModifyLoadBalancerAttributes",) 

462 

463 def validate(self): 

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

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

466 raise PolicyValidationError(( 

467 "alb logging enablement requires `bucket` " 

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

469 return self 

470 

471 def process(self, resources): 

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

473 for elb in resources: 

474 elb_arn = elb['LoadBalancerArn'] 

475 attributes = [{ 

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

477 'Value': ( 

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

479 

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

481 attributes.append({ 

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

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

484 

485 prefix_template = self.data['prefix'] 

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

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

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

489 info['LoadBalancerName'] = elb['LoadBalancerName'] 

490 

491 attributes.append({ 

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

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

494 

495 self.manager.retry( 

496 client.modify_load_balancer_attributes, 

497 LoadBalancerArn=elb_arn, Attributes=attributes) 

498 

499 

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

501class AppELBMarkForOpAction(tags.TagDelayedAction): 

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

503 

504 :example: 

505 

506 .. code-block:: yaml 

507 

508 policies: 

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

510 resource: app-elb 

511 filters: 

512 - "tag:custodian_elb_cleanup": absent 

513 - State: failed 

514 actions: 

515 - type: mark-for-op 

516 tag: custodian_elb_cleanup 

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

518 op: delete 

519 days: 1 

520 """ 

521 

522 batch_size = 1 

523 

524 

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

526class AppELBTagAction(tags.Tag): 

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

528 

529 :example: 

530 

531 .. code-block:: yaml 

532 

533 policies: 

534 - name: appelb-create-required-tag 

535 resource: app-elb 

536 filters: 

537 - "tag:RequiredTag": absent 

538 actions: 

539 - type: tag 

540 key: RequiredTag 

541 value: RequiredValue 

542 """ 

543 

544 batch_size = 1 

545 permissions = ("elasticloadbalancing:AddTags",) 

546 

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

548 client.add_tags( 

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

550 Tags=ts) 

551 

552 

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

554class AppELBRemoveTagAction(tags.RemoveTag): 

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

556 

557 :example: 

558 

559 .. code-block:: yaml 

560 

561 policies: 

562 - name: appelb-delete-expired-tag 

563 resource: app-elb 

564 filters: 

565 - "tag:ExpiredTag": present 

566 actions: 

567 - type: remove-tag 

568 tags: ["ExpiredTag"] 

569 """ 

570 

571 batch_size = 1 

572 permissions = ("elasticloadbalancing:RemoveTags",) 

573 

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

575 client.remove_tags( 

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

577 TagKeys=tag_keys) 

578 

579 

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

581class AppELBDeleteAction(BaseAction): 

582 """Action to delete an ELB 

583 

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

585 to the rule 

586 

587 :example: 

588 

589 .. code-block:: yaml 

590 

591 policies: 

592 - name: appelb-delete-failed-elb 

593 resource: app-elb 

594 filters: 

595 - State: failed 

596 actions: 

597 - delete 

598 """ 

599 

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

601 permissions = ( 

602 "elasticloadbalancing:DeleteLoadBalancer", 

603 "elasticloadbalancing:ModifyLoadBalancerAttributes",) 

604 

605 def process(self, load_balancers): 

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

607 for lb in load_balancers: 

608 self.process_alb(client, lb) 

609 

610 def process_alb(self, client, alb): 

611 try: 

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

613 client.modify_load_balancer_attributes( 

614 LoadBalancerArn=alb['LoadBalancerArn'], 

615 Attributes=[{ 

616 'Key': 'deletion_protection.enabled', 

617 'Value': 'false', 

618 }]) 

619 self.manager.retry( 

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

621 except client.exceptions.LoadBalancerNotFoundException: 

622 pass 

623 except ( 

624 client.exceptions.OperationNotPermittedException, 

625 client.exceptions.ResourceInUseException 

626 ) as e: 

627 self.log.warning( 

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

629 alb['LoadBalancerArn'], e) 

630 

631 

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

633class AppELBModifyAttributes(BaseAction): 

634 """Modify load balancer attributes. 

635 

636 :example: 

637 

638 .. code-block:: yaml 

639 

640 policies: 

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

642 resource: app-elb 

643 filters: 

644 - type: attributes 

645 key: "deletion_protection.enabled" 

646 value: false 

647 actions: 

648 - type: modify-attributes 

649 attributes: 

650 "deletion_protection.enabled": "true" 

651 "idle_timeout.timeout_seconds": 120 

652 """ 

653 schema = { 

654 'type': 'object', 

655 'additionalProperties': False, 

656 'properties': { 

657 'type': { 

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

659 'attributes': { 

660 'type': 'object', 

661 'additionalProperties': False, 

662 'properties': { 

663 'access_logs.s3.enabled': { 

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

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

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

667 'deletion_protection.enabled': { 

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

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

670 'routing.http.desync_mitigation_mode': { 

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

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

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

674 'routing.http2.enabled': { 

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

676 'load_balancing.cross_zone.enabled': { 

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

678 }, 

679 }, 

680 }, 

681 } 

682 permissions = ("elasticloadbalancing:ModifyLoadBalancerAttributes",) 

683 

684 def process(self, resources): 

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

686 for appelb in resources: 

687 self.manager.retry( 

688 client.modify_load_balancer_attributes, 

689 LoadBalancerArn=appelb['LoadBalancerArn'], 

690 Attributes=[ 

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

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

693 ], 

694 ignore_err_codes=('LoadBalancerNotFoundException',), 

695 ) 

696 return resources 

697 

698 

699class AppELBListenerFilterBase: 

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

701 """ 

702 permissions = ("elasticloadbalancing:DescribeListeners",) 

703 

704 def initialize(self, albs): 

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

706 self.listener_map = defaultdict(list) 

707 for alb in albs: 

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

709 LoadBalancerArn=alb['LoadBalancerArn'], 

710 ignore_err_codes=('LoadBalancerNotFoundException',)) 

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

712 

713 

714def parse_attribute_value(v): 

715 if v.isdigit(): 

716 v = int(v) 

717 elif v == 'true': 

718 v = True 

719 elif v == 'false': 

720 v = False 

721 return v 

722 

723 

724def serialize_attribute_value(v): 

725 if v is True: 

726 return 'true' 

727 elif v is False: 

728 return 'false' 

729 elif isinstance(v, int): 

730 return str(v) 

731 return v 

732 

733 

734class AppELBAttributeFilterBase: 

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

736 """ 

737 

738 def initialize(self, albs): 

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

740 

741 def _process_attributes(alb): 

742 if 'Attributes' not in alb: 

743 alb['Attributes'] = {} 

744 results = client.describe_load_balancer_attributes( 

745 LoadBalancerArn=alb['LoadBalancerArn']) 

746 # flatten out the list of dicts and cast 

747 for pair in results['Attributes']: 

748 k = pair['Key'] 

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

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

751 

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

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

754 

755 

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

757class IsLoggingFilter(Filter, AppELBAttributeFilterBase): 

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

759 bucket and prefix are optional 

760 

761 :example: 

762 

763 .. code-block:: yaml 

764 

765 policies: 

766 - name: alb-is-logging-test 

767 resource: app-elb 

768 filters: 

769 - type: is-logging 

770 

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

772 resource: app-elb 

773 filters: 

774 - type: is-logging 

775 bucket: prodlogs 

776 prefix: alblogs 

777 

778 """ 

779 permissions = ("elasticloadbalancing:DescribeLoadBalancerAttributes",) 

780 schema = type_schema('is-logging', 

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

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

783 ) 

784 

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

786 self.initialize(resources) 

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

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

789 

790 return [alb for alb in resources 

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

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

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

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

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

796 ] 

797 

798 

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

800class IsNotLoggingFilter(Filter, AppELBAttributeFilterBase): 

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

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

803 

804 :example: 

805 

806 .. code-block:: yaml 

807 

808 policies: 

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

810 resource: app-elb 

811 filters: 

812 - type: is-not-logging 

813 

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

815 resource: app-elb 

816 filters: 

817 - type: is-not-logging 

818 bucket: prodlogs 

819 prefix: alblogs 

820 

821 """ 

822 permissions = ("elasticloadbalancing:DescribeLoadBalancerAttributes",) 

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

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

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

826 ) 

827 

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

829 self.initialize(resources) 

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

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

832 

833 return [alb for alb in resources 

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

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

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

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

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

839 

840 

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

842class CheckAttributes(ValueFilter, AppELBAttributeFilterBase): 

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

844 

845 :example: 

846 

847 .. code-block:: yaml 

848 

849 policies: 

850 - name: alb-http2-enabled 

851 resource: app-elb 

852 filters: 

853 - type: attributes 

854 key: routing.http2.enabled 

855 value: true 

856 op: eq 

857 """ 

858 annotate: False # no annotation from value Filter 

859 permissions = ("elasticloadbalancing:DescribeLoadBalancerAttributes",) 

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

861 schema_alias = False 

862 

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

864 self.augment(resources) 

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

866 

867 def augment(self, resources): 

868 self.initialize(resources) 

869 

870 def __call__(self, r): 

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

872 

873 

874class AppELBTargetGroupFilterBase: 

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

876 """ 

877 

878 def initialize(self, albs): 

879 self.target_group_map = defaultdict(list) 

880 target_groups = self.manager.get_resource_manager( 

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

882 for target_group in target_groups: 

883 for load_balancer_arn in target_group['LoadBalancerArns']: 

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

885 

886 

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

888class AppELBListenerFilter(ValueFilter, AppELBListenerFilterBase): 

889 """Filter ALB based on matching listener attributes 

890 

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

892 

893 :example: 

894 

895 .. code-block:: yaml 

896 

897 policies: 

898 - name: app-elb-invalid-ciphers 

899 resource: app-elb 

900 filters: 

901 - type: listener 

902 key: Protocol 

903 value: HTTPS 

904 - type: listener 

905 key: SslPolicy 

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

907 op: ni 

908 matched: true 

909 actions: 

910 - type: modify-listener 

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

912 """ 

913 

914 schema = type_schema( 

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

916 schema_alias = False 

917 permissions = ("elasticloadbalancing:DescribeLoadBalancerAttributes",) 

918 

919 def validate(self): 

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

921 return 

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

923 found = False 

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

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

926 found = True 

927 break 

928 if not found: 

929 raise PolicyValidationError( 

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

931 self.manager.data,)) 

932 return self 

933 

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

935 self.initialize(albs) 

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

937 

938 def __call__(self, alb): 

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

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

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

942 

943 found_listeners = False 

944 for listener in listeners: 

945 if self.match(listener): 

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

947 found_listeners = True 

948 return found_listeners 

949 

950 

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

952class AppELBModifyListenerPolicy(BaseAction): 

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

954 

955 :example: 

956 

957 .. code-block:: yaml 

958 

959 policies: 

960 - name: appelb-modify-listener 

961 resource: app-elb 

962 filters: 

963 - type: listener 

964 key: Protocol 

965 value: HTTP 

966 actions: 

967 - type: modify-listener 

968 protocol: HTTPS 

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

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

971 1234-1234-1234-123456789012" 

972 """ 

973 

974 schema = type_schema( 

975 'modify-listener', 

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

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

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

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

980 ) 

981 

982 permissions = ("elasticloadbalancing:ModifyListener",) 

983 

984 def validate(self): 

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

986 if f.type == 'listener': 

987 return self 

988 raise PolicyValidationError( 

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

990 self.manager.data,)) 

991 

992 def process(self, load_balancers): 

993 args = {} 

994 if 'port' in self.data: 

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

996 if 'protocol' in self.data: 

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

998 if 'sslpolicy' in self.data: 

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

1000 if 'certificate' in self.data: 

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

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

1003 

1004 for alb in load_balancers: 

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

1006 client.modify_listener( 

1007 ListenerArn=matched_listener['ListenerArn'], 

1008 **args) 

1009 

1010 

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

1012class AppELBModifyVpcSecurityGroups(ModifyVpcSecurityGroupsAction): 

1013 

1014 permissions = ("elasticloadbalancing:SetSecurityGroups",) 

1015 

1016 def process(self, albs): 

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

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

1019 

1020 for idx, i in enumerate(albs): 

1021 try: 

1022 client.set_security_groups( 

1023 LoadBalancerArn=i['LoadBalancerArn'], 

1024 SecurityGroups=groups[idx]) 

1025 except client.exceptions.LoadBalancerNotFoundException: 

1026 continue 

1027 

1028 

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

1030class AppELBHealthCheckProtocolMismatchFilter(Filter, 

1031 AppELBTargetGroupFilterBase): 

1032 """Filter AppELBs with mismatched health check protocols 

1033 

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

1035 does not match the load balancer health check protocol 

1036 

1037 :example: 

1038 

1039 .. code-block:: yaml 

1040 

1041 policies: 

1042 - name: appelb-healthcheck-mismatch 

1043 resource: app-elb 

1044 filters: 

1045 - healthcheck-protocol-mismatch 

1046 """ 

1047 

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

1049 permissions = ("elasticloadbalancing:DescribeTargetGroups",) 

1050 

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

1052 def _healthcheck_protocol_mismatch(alb): 

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

1054 if (target_group['Protocol'] != 

1055 target_group['HealthCheckProtocol']): 

1056 return True 

1057 

1058 return False 

1059 

1060 self.initialize(albs) 

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

1062 

1063 

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

1065class AppELBTargetGroupFilter(ValueFilter, AppELBTargetGroupFilterBase): 

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

1067 

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

1069 schema_alias = False 

1070 permissions = ("elasticloadbalancing:DescribeTargetGroups",) 

1071 

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

1073 self.initialize(albs) 

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

1075 

1076 def __call__(self, alb): 

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

1078 return self.match(target_groups) 

1079 

1080 

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

1082class AppELBDefaultVpcFilter(net_filters.DefaultVpcBase): 

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

1084 

1085 :example: 

1086 

1087 .. code-block:: yaml 

1088 

1089 policies: 

1090 - name: appelb-in-default-vpc 

1091 resource: app-elb 

1092 filters: 

1093 - default-vpc 

1094 """ 

1095 

1096 schema = type_schema('default-vpc') 

1097 

1098 def __call__(self, alb): 

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

1100 

1101 

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

1103class AppELBTargetGroup(QueryResourceManager): 

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

1105 """ 

1106 

1107 class resource_type(TypeInfo): 

1108 service = 'elbv2' 

1109 arn_type = 'target-group' 

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

1111 name = 'TargetGroupName' 

1112 id = 'TargetGroupArn' 

1113 permission_prefix = 'elasticloadbalancing' 

1114 cfn_type = 'AWS::ElasticLoadBalancingV2::TargetGroup' 

1115 

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

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

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

1119 

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

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

1122 

1123 @classmethod 

1124 def get_permissions(cls): 

1125 # override as the service is not the iam prefix 

1126 return ("elasticloadbalancing:DescribeTargetGroups", 

1127 "elasticloadbalancing:DescribeTags") 

1128 

1129 def augment(self, target_groups): 

1130 client = local_session(self.session_factory).client('elbv2') 

1131 

1132 def _describe_target_group_health(target_group): 

1133 result = self.retry(client.describe_target_health, 

1134 TargetGroupArn=target_group['TargetGroupArn']) 

1135 target_group['TargetHealthDescriptions'] = result[ 

1136 'TargetHealthDescriptions'] 

1137 

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

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

1140 

1141 _describe_target_group_tags( 

1142 target_groups, self.session_factory, 

1143 self.executor_factory, self.retry) 

1144 return target_groups 

1145 

1146 

1147def _describe_target_group_tags(target_groups, session_factory, 

1148 executor_factory, retry): 

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

1150 

1151 def _process_tags(target_group_set): 

1152 target_group_map = { 

1153 target_group['TargetGroupArn']: 

1154 target_group for target_group in target_group_set 

1155 } 

1156 

1157 results = retry( 

1158 client.describe_tags, 

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

1160 for tag_desc in results['TagDescriptions']: 

1161 if ('ResourceArn' in tag_desc and 

1162 tag_desc['ResourceArn'] in target_group_map): 

1163 target_group_map[ 

1164 tag_desc['ResourceArn'] 

1165 ]['Tags'] = tag_desc['Tags'] 

1166 

1167 with executor_factory(max_workers=2) as w: 

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

1169 

1170 

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

1172class AppELBTargetGroupMarkForOpAction(tags.TagDelayedAction): 

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

1174 

1175 

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

1177class AppELBTargetGroupTagAction(tags.Tag): 

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

1179 

1180 :example: 

1181 

1182 .. code-block:: yaml 

1183 

1184 policies: 

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

1186 resource: app-elb-target-group 

1187 filters: 

1188 - "tag:RequiredTag": absent 

1189 actions: 

1190 - type: tag 

1191 key: RequiredTag 

1192 value: RequiredValue 

1193 """ 

1194 

1195 batch_size = 1 

1196 permissions = ("elasticloadbalancing:AddTags",) 

1197 

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

1199 client.add_tags( 

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

1201 Tags=ts) 

1202 

1203 

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

1205class AppELBTargetGroupRemoveTagAction(tags.RemoveTag): 

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

1207 

1208 :example: 

1209 

1210 .. code-block:: yaml 

1211 

1212 policies: 

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

1214 resource: app-elb-target-group 

1215 filters: 

1216 - "tag:ExpiredTag": present 

1217 actions: 

1218 - type: remove-tag 

1219 tags: ["ExpiredTag"] 

1220 """ 

1221 

1222 batch_size = 1 

1223 permissions = ("elasticloadbalancing:RemoveTags",) 

1224 

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

1226 client.remove_tags( 

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

1228 TagKeys=tag_keys) 

1229 

1230 

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

1232class AppELBTargetGroupDefaultVpcFilter(net_filters.DefaultVpcBase): 

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

1234 

1235 :example: 

1236 

1237 .. code-block:: yaml 

1238 

1239 policies: 

1240 - name: appelb-targetgroups-default-vpc 

1241 resource: app-elb-target-group 

1242 filters: 

1243 - default-vpc 

1244 """ 

1245 

1246 schema = type_schema('default-vpc') 

1247 

1248 def __call__(self, target_group): 

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

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

1251 

1252 

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

1254class AppELBTargetGroupDeleteAction(BaseAction): 

1255 """Action to delete ELB target group 

1256 

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

1258 deletion of any app elb target groups. 

1259 

1260 :example: 

1261 

1262 .. code-block:: yaml 

1263 

1264 policies: 

1265 - name: appelb-targetgroups-delete-unused 

1266 resource: app-elb-target-group 

1267 filters: 

1268 - "tag:SomeTag": absent 

1269 actions: 

1270 - delete 

1271 """ 

1272 

1273 schema = type_schema('delete') 

1274 permissions = ('elasticloadbalancing:DeleteTargetGroup',) 

1275 

1276 def process(self, resources): 

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

1278 for tg in resources: 

1279 self.process_target_group(client, tg) 

1280 

1281 def process_target_group(self, client, target_group): 

1282 self.manager.retry( 

1283 client.delete_target_group, 

1284 TargetGroupArn=target_group['TargetGroupArn']) 

1285 

1286 

1287class TargetGroupAttributeFilterBase: 

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

1289 """ 

1290 

1291 def initialize(self, tgs): 

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

1293 

1294 def _process_attributes(tg): 

1295 if 'c7n:TargetGroupAttributes' not in tg: 

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

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

1298 TargetGroupArn=tg['TargetGroupArn'], 

1299 ignore_err_codes=('TargetGroupNotFoundException',)) 

1300 # flatten out the list of dicts and cast 

1301 for pair in results['Attributes']: 

1302 k = pair['Key'] 

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

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

1305 

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

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

1308 

1309 

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

1311class TargetGroupCheckAttributes(ValueFilter, TargetGroupAttributeFilterBase): 

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

1313 

1314 :example: 

1315 

1316 .. code-block:: yaml 

1317 

1318 policies: 

1319 - name: target-group-check-attributes 

1320 resource: app-elb-target-group 

1321 filters: 

1322 - type: attributes 

1323 key: preserve_client_ip.enabled 

1324 value: True 

1325 op: eq 

1326 """ 

1327 annotate: False # no annotation from value Filter 

1328 permissions = ("elasticloadbalancing:DescribeTargetGroupAttributes",) 

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

1330 schema_alias = False 

1331 

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

1333 self.augment(resources) 

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

1335 

1336 def augment(self, resources): 

1337 self.initialize(resources) 

1338 

1339 def __call__(self, r): 

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

1341 

1342 

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

1344class AppELBTargetGroupModifyAttributes(BaseAction): 

1345 """Modify target group attributes. 

1346 

1347 :example: 

1348 

1349 .. code-block:: yaml 

1350 

1351 policies: 

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

1353 resource: app-elb-target-group 

1354 filters: 

1355 - type: attributes 

1356 key: "preserve_client_ip.enabled" 

1357 value: False 

1358 actions: 

1359 - type: modify-attributes 

1360 attributes: 

1361 "preserve_client_ip.enabled": "true" 

1362 """ 

1363 schema = { 

1364 'type': 'object', 

1365 'additionalProperties': False, 

1366 'properties': { 

1367 'type': { 

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

1369 'attributes': { 

1370 'type': 'object', 

1371 'additionalProperties': False, 

1372 'properties': { 

1373 'proxy_protocol_v2.enabled': { 

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

1375 'preserve_client_ip.enabled': { 

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

1377 'stickiness.enabled': { 

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

1379 'lambda.multi_value_headers.enabled': { 

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

1381 'deregistration_delay.connection_termination.enabled': { 

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

1383 'target_group_health.unhealthy_state_routing.' 

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

1385 'target_group_health.unhealthy_state_routing.' 

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

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

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

1389 'type': 'string'}, 

1390 'stickiness.type': { 

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

1392 'source_ip_dest_ip', 'source_ip_dest_ip_proto']}, 

1393 'load_balancing.cross_zone.enabled': { 

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

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

1396 'type': 'string'}, 

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

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

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

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

1401 'load_balancing.algorithm.type': { 

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

1403 'target_failover.on_deregistration': { 

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

1405 'target_failover.on_unhealthy': { 

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

1407 }, 

1408 }, 

1409 }, 

1410 } 

1411 permissions = ("elasticloadbalancing:ModifyTargetGroupAttributes",) 

1412 

1413 def process(self, resources): 

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

1415 self.log.info(resources) 

1416 for targetgroup in resources: 

1417 self.manager.retry( 

1418 client.modify_target_group_attributes, 

1419 TargetGroupArn=targetgroup['TargetGroupArn'], 

1420 Attributes=[ 

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

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

1423 ], 

1424 ignore_err_codes=('TargetGroupNotFoundException',), 

1425 ) 

1426 return resources