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

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

481 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 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 permissions_augment = ("elasticloadbalancing:DescribeTags",) 

91 

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

93 source_mapping = { 

94 'describe': DescribeAppElb, 

95 'config': ConfigAppElb 

96 } 

97 

98 @classmethod 

99 def get_permissions(cls): 

100 # override as the service is not the iam prefix 

101 return ("elasticloadbalancing:DescribeLoadBalancers", 

102 "elasticloadbalancing:DescribeLoadBalancerAttributes", 

103 "elasticloadbalancing:DescribeTags") 

104 

105 

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

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

108 

109 def _process_tags(alb_set): 

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

111 

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

113 for tag_desc in results['TagDescriptions']: 

114 if ('ResourceArn' in tag_desc and 

115 tag_desc['ResourceArn'] in alb_map): 

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

117 

118 with executor_factory(max_workers=2) as w: 

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

120 

121 

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

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

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

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

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

127 

128 

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

130class AppElbMetrics(MetricsFilter): 

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

132 

133 Note application and network load balancers use different Cloud 

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

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

136 filter should be used to ensure only targeting a particular 

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

138 

139 See available application load balancer metrics here 

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

141 

142 See available network load balancer metrics here. 

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

144 

145 

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

147 the namespace parameter to the filter. 

148 

149 .. code-block:: yaml 

150 

151 policies: 

152 - name: net-lb-underutilized 

153 resource: app-elb 

154 filters: 

155 - Type: network 

156 - type: metrics 

157 name: ActiveFlowCount 

158 namespace: AWS/NetworkELB 

159 statistics: Sum 

160 days: 14 

161 value: 100 

162 op: less-than 

163 """ 

164 

165 def get_dimensions(self, resource): 

166 return [{ 

167 'Name': self.model.dimension, 

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

169 

170 

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

172class SecurityGroupFilter(net_filters.SecurityGroupFilter): 

173 

174 RelatedIdsExpression = "SecurityGroups[]" 

175 

176 

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

178class SubnetFilter(net_filters.SubnetFilter): 

179 

180 RelatedIdsExpression = "AvailabilityZones[].SubnetId" 

181 

182 

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

184class VpcFilter(net_filters.VpcFilter): 

185 

186 RelatedIdsExpression = "VpcId" 

187 

188 

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

190class WafEnabled(WafClassicRegionalFilterBase): 

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

192 

193 :example: 

194 

195 .. code-block:: yaml 

196 

197 policies: 

198 - name: filter-elb-waf-regional 

199 resource: app-elb 

200 filters: 

201 - type: waf-enabled 

202 state: false 

203 web-acl: test 

204 """ 

205 

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

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

208 def get_associated_web_acl(self, resource): 

209 return self.get_web_acl_from_associations( 

210 'APPLICATION_LOAD_BALANCER', 

211 resource['LoadBalancerArn'] 

212 ) 

213 

214 

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

216class WafV2Enabled(WafV2FilterBase): 

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

218 

219 Supports regex expression for web-acl. 

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

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

222 

223 :example: 

224 

225 .. code-block:: yaml 

226 

227 policies: 

228 - name: filter-wafv2-elb 

229 resource: app-elb 

230 filters: 

231 - type: wafv2-enabled 

232 state: false 

233 web-acl: testv2 

234 

235 - name: filter-wafv2-elb-regex 

236 resource: app-elb 

237 filters: 

238 - type: wafv2-enabled 

239 state: false 

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

241 """ 

242 

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

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

245 def get_associated_web_acl(self, resource): 

246 return self.get_web_acl_from_associations( 

247 'APPLICATION_LOAD_BALANCER', 

248 resource['LoadBalancerArn'] 

249 ) 

250 

251 

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

253class SetWaf(BaseAction): 

254 """Enable wafv2 protection on Application LoadBalancer. 

255 

256 :example: 

257 

258 .. code-block:: yaml 

259 

260 policies: 

261 - name: set-waf-for-elb 

262 resource: app-elb 

263 filters: 

264 - type: waf-enabled 

265 state: false 

266 web-acl: test 

267 actions: 

268 - type: set-waf 

269 state: true 

270 web-acl: test 

271 

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

273 resource: app-elb 

274 filters: 

275 - type: wafv2-enabled 

276 state: true 

277 actions: 

278 - type: set-waf 

279 state: true 

280 web-acl: test 

281 

282 """ 

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

284 

285 schema = type_schema( 

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

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

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

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

290 

291 def validate(self): 

292 found = False 

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

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

295 found = True 

296 break 

297 if not found: 

298 # try to ensure idempotent usage 

299 raise PolicyValidationError( 

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

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

302 return self 

303 

304 def process(self, resources): 

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

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

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

308 target_acl_id = name_id_map.get(target_acl, target_acl) 

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

310 

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

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

313 

314 client = local_session( 

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

316 

317 arn_key = self.manager.resource_type.id 

318 

319 # TODO implement force to reassociate. 

320 # TODO investigate limits on waf association. 

321 for r in resources: 

322 if state: 

323 client.associate_web_acl( 

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

325 else: 

326 client.disassociate_web_acl( 

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

328 

329 

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

331class SetWafV2(BaseAction): 

332 """Enable wafv2 protection on Application LoadBalancer. 

333 

334 Supports regex expression for web-acl 

335 

336 :example: 

337 

338 .. code-block:: yaml 

339 

340 policies: 

341 - name: set-wafv2-for-elb 

342 resource: app-elb 

343 filters: 

344 - type: wafv2-enabled 

345 state: false 

346 web-acl: testv2 

347 actions: 

348 - type: set-wafv2 

349 state: true 

350 web-acl: testv2 

351 

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

353 resource: app-elb 

354 filters: 

355 - type: waf-enabled 

356 state: true 

357 actions: 

358 - type: set-wafv2 

359 state: true 

360 

361 policies: 

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

363 resource: app-elb 

364 filters: 

365 - type: wafv2-enabled 

366 state: false 

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

368 actions: 

369 - type: set-wafv2 

370 state: true 

371 web-acl: FMManagedWebACLV2-?FMS-TestWebACL 

372 

373 """ 

374 permissions = ('wafv2:AssociateWebACL', 

375 'wafv2:DisassociateWebACL', 

376 'wafv2:ListWebACLs') 

377 

378 schema = type_schema( 

379 'set-wafv2', **{ 

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

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

382 

383 retry = staticmethod(get_retry(( 

384 'ThrottlingException', 

385 'RequestLimitExceeded', 

386 'Throttled', 

387 'ThrottledException', 

388 'Throttling', 

389 'Client.RequestLimitExceeded'))) 

390 

391 def validate(self): 

392 found = False 

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

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

395 found = True 

396 break 

397 if not found: 

398 # try to ensure idempotent usage 

399 raise PolicyValidationError( 

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

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

402 return self 

403 

404 def process(self, resources): 

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

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

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

408 

409 target_acl_id = '' 

410 if state: 

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

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

413 re.match(target_acl, k)] 

414 if len(target_acl_ids) != 1: 

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

416 f'multiple webacls') 

417 target_acl_id = target_acl_ids[0] 

418 

419 client = local_session( 

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

421 

422 arn_key = self.manager.resource_type.id 

423 

424 # TODO implement force to reassociate. 

425 # TODO investigate limits on waf association. 

426 for r in resources: 

427 if state: 

428 self.retry(client.associate_web_acl, 

429 WebACLArn=target_acl_id, 

430 ResourceArn=r[arn_key]) 

431 else: 

432 self.retry(client.disassociate_web_acl, 

433 ResourceArn=r[arn_key]) 

434 

435 

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

437class SetS3Logging(BaseAction): 

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

439 

440 :example: 

441 

442 .. code-block:: yaml 

443 

444 policies: 

445 - name: elbv2-test 

446 resource: app-elb 

447 filters: 

448 - type: is-not-logging 

449 actions: 

450 - type: set-s3-logging 

451 bucket: elbv2logtest 

452 prefix: dahlogs 

453 state: enabled 

454 """ 

455 schema = type_schema( 

456 'set-s3-logging', 

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

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

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

460 required=('state',)) 

461 

462 permissions = ("elasticloadbalancing:ModifyLoadBalancerAttributes",) 

463 

464 def validate(self): 

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

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

467 raise PolicyValidationError(( 

468 "alb logging enablement requires `bucket` " 

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

470 return self 

471 

472 def process(self, resources): 

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

474 for elb in resources: 

475 elb_arn = elb['LoadBalancerArn'] 

476 attributes = [{ 

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

478 'Value': ( 

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

480 

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

482 attributes.append({ 

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

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

485 

486 prefix_template = self.data['prefix'] 

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

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

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

490 info['LoadBalancerName'] = elb['LoadBalancerName'] 

491 

492 attributes.append({ 

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

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

495 

496 self.manager.retry( 

497 client.modify_load_balancer_attributes, 

498 LoadBalancerArn=elb_arn, Attributes=attributes) 

499 

500 

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

502class AppELBMarkForOpAction(tags.TagDelayedAction): 

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

504 

505 :example: 

506 

507 .. code-block:: yaml 

508 

509 policies: 

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

511 resource: app-elb 

512 filters: 

513 - "tag:custodian_elb_cleanup": absent 

514 - State: failed 

515 actions: 

516 - type: mark-for-op 

517 tag: custodian_elb_cleanup 

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

519 op: delete 

520 days: 1 

521 """ 

522 

523 batch_size = 1 

524 

525 

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

527class AppELBTagAction(tags.Tag): 

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

529 

530 :example: 

531 

532 .. code-block:: yaml 

533 

534 policies: 

535 - name: appelb-create-required-tag 

536 resource: app-elb 

537 filters: 

538 - "tag:RequiredTag": absent 

539 actions: 

540 - type: tag 

541 key: RequiredTag 

542 value: RequiredValue 

543 """ 

544 

545 batch_size = 1 

546 permissions = ("elasticloadbalancing:AddTags",) 

547 

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

549 client.add_tags( 

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

551 Tags=ts) 

552 

553 

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

555class AppELBRemoveTagAction(tags.RemoveTag): 

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

557 

558 :example: 

559 

560 .. code-block:: yaml 

561 

562 policies: 

563 - name: appelb-delete-expired-tag 

564 resource: app-elb 

565 filters: 

566 - "tag:ExpiredTag": present 

567 actions: 

568 - type: remove-tag 

569 tags: ["ExpiredTag"] 

570 """ 

571 

572 batch_size = 1 

573 permissions = ("elasticloadbalancing:RemoveTags",) 

574 

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

576 client.remove_tags( 

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

578 TagKeys=tag_keys) 

579 

580 

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

582class AppELBDeleteAction(BaseAction): 

583 """Action to delete an ELB 

584 

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

586 to the rule 

587 

588 :example: 

589 

590 .. code-block:: yaml 

591 

592 policies: 

593 - name: appelb-delete-failed-elb 

594 resource: app-elb 

595 filters: 

596 - State: failed 

597 actions: 

598 - delete 

599 """ 

600 

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

602 permissions = ( 

603 "elasticloadbalancing:DeleteLoadBalancer", 

604 "elasticloadbalancing:ModifyLoadBalancerAttributes",) 

605 

606 def process(self, load_balancers): 

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

608 for lb in load_balancers: 

609 self.process_alb(client, lb) 

610 

611 def process_alb(self, client, alb): 

612 try: 

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

614 client.modify_load_balancer_attributes( 

615 LoadBalancerArn=alb['LoadBalancerArn'], 

616 Attributes=[{ 

617 'Key': 'deletion_protection.enabled', 

618 'Value': 'false', 

619 }]) 

620 self.manager.retry( 

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

622 except client.exceptions.LoadBalancerNotFoundException: 

623 pass 

624 except ( 

625 client.exceptions.OperationNotPermittedException, 

626 client.exceptions.ResourceInUseException 

627 ) as e: 

628 self.log.warning( 

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

630 alb['LoadBalancerArn'], e) 

631 

632 

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

634class AppELBModifyAttributes(BaseAction): 

635 """Modify load balancer attributes. 

636 

637 :example: 

638 

639 .. code-block:: yaml 

640 

641 policies: 

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

643 resource: app-elb 

644 filters: 

645 - type: attributes 

646 key: "deletion_protection.enabled" 

647 value: false 

648 actions: 

649 - type: modify-attributes 

650 attributes: 

651 "deletion_protection.enabled": "true" 

652 "idle_timeout.timeout_seconds": 120 

653 """ 

654 schema = { 

655 'type': 'object', 

656 'additionalProperties': False, 

657 'properties': { 

658 'type': { 

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

660 'attributes': { 

661 'type': 'object', 

662 'additionalProperties': False, 

663 'properties': { 

664 'access_logs.s3.enabled': { 

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

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

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

668 'deletion_protection.enabled': { 

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

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

671 'routing.http.desync_mitigation_mode': { 

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

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

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

675 'routing.http2.enabled': { 

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

677 'load_balancing.cross_zone.enabled': { 

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

679 }, 

680 }, 

681 }, 

682 } 

683 permissions = ("elasticloadbalancing:ModifyLoadBalancerAttributes",) 

684 

685 def process(self, resources): 

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

687 for appelb in resources: 

688 self.manager.retry( 

689 client.modify_load_balancer_attributes, 

690 LoadBalancerArn=appelb['LoadBalancerArn'], 

691 Attributes=[ 

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

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

694 ], 

695 ignore_err_codes=('LoadBalancerNotFoundException',), 

696 ) 

697 return resources 

698 

699 

700class AppELBListenerFilterBase: 

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

702 """ 

703 permissions = ("elasticloadbalancing:DescribeListeners",) 

704 

705 def initialize(self, albs): 

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

707 self.listener_map = defaultdict(list) 

708 for alb in albs: 

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

710 LoadBalancerArn=alb['LoadBalancerArn'], 

711 ignore_err_codes=('LoadBalancerNotFoundException',)) 

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

713 

714 

715def parse_attribute_value(v): 

716 if v.isdigit(): 

717 v = int(v) 

718 elif v == 'true': 

719 v = True 

720 elif v == 'false': 

721 v = False 

722 return v 

723 

724 

725def serialize_attribute_value(v): 

726 if v is True: 

727 return 'true' 

728 elif v is False: 

729 return 'false' 

730 elif isinstance(v, int): 

731 return str(v) 

732 return v 

733 

734 

735class AppELBAttributeFilterBase: 

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

737 """ 

738 

739 def initialize(self, albs): 

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

741 

742 def _process_attributes(alb): 

743 if 'Attributes' not in alb: 

744 alb['Attributes'] = {} 

745 results = client.describe_load_balancer_attributes( 

746 LoadBalancerArn=alb['LoadBalancerArn']) 

747 # flatten out the list of dicts and cast 

748 for pair in results['Attributes']: 

749 k = pair['Key'] 

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

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

752 

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

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

755 

756 

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

758class IsLoggingFilter(Filter, AppELBAttributeFilterBase): 

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

760 bucket and prefix are optional 

761 

762 :example: 

763 

764 .. code-block:: yaml 

765 

766 policies: 

767 - name: alb-is-logging-test 

768 resource: app-elb 

769 filters: 

770 - type: is-logging 

771 

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

773 resource: app-elb 

774 filters: 

775 - type: is-logging 

776 bucket: prodlogs 

777 prefix: alblogs 

778 

779 """ 

780 permissions = ("elasticloadbalancing:DescribeLoadBalancerAttributes",) 

781 schema = type_schema('is-logging', 

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

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

784 ) 

785 

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

787 self.initialize(resources) 

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

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

790 

791 return [alb for alb in resources 

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

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

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

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

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

797 ] 

798 

799 

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

801class IsNotLoggingFilter(Filter, AppELBAttributeFilterBase): 

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

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

804 

805 :example: 

806 

807 .. code-block:: yaml 

808 

809 policies: 

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

811 resource: app-elb 

812 filters: 

813 - type: is-not-logging 

814 

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

816 resource: app-elb 

817 filters: 

818 - type: is-not-logging 

819 bucket: prodlogs 

820 prefix: alblogs 

821 

822 """ 

823 permissions = ("elasticloadbalancing:DescribeLoadBalancerAttributes",) 

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

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

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

827 ) 

828 

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

830 self.initialize(resources) 

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

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

833 

834 return [alb for alb in resources 

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

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

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

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

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

840 

841 

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

843class CheckAttributes(ValueFilter, AppELBAttributeFilterBase): 

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

845 

846 :example: 

847 

848 .. code-block:: yaml 

849 

850 policies: 

851 - name: alb-http2-enabled 

852 resource: app-elb 

853 filters: 

854 - type: attributes 

855 key: routing.http2.enabled 

856 value: true 

857 op: eq 

858 """ 

859 annotate: False # no annotation from value Filter 

860 permissions = ("elasticloadbalancing:DescribeLoadBalancerAttributes",) 

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

862 schema_alias = False 

863 

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

865 self.augment(resources) 

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

867 

868 def augment(self, resources): 

869 self.initialize(resources) 

870 

871 def __call__(self, r): 

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

873 

874 

875class AppELBTargetGroupFilterBase: 

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

877 """ 

878 

879 def initialize(self, albs): 

880 self.target_group_map = defaultdict(list) 

881 target_groups = self.manager.get_resource_manager( 

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

883 for target_group in target_groups: 

884 for load_balancer_arn in target_group['LoadBalancerArns']: 

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

886 

887 

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

889class AppELBListenerFilter(ValueFilter, AppELBListenerFilterBase): 

890 """Filter ALB based on matching listener attributes 

891 

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

893 

894 :example: 

895 

896 .. code-block:: yaml 

897 

898 policies: 

899 - name: app-elb-invalid-ciphers 

900 resource: app-elb 

901 filters: 

902 - type: listener 

903 key: Protocol 

904 value: HTTPS 

905 - type: listener 

906 key: SslPolicy 

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

908 op: ni 

909 matched: true 

910 actions: 

911 - type: modify-listener 

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

913 """ 

914 

915 schema = type_schema( 

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

917 schema_alias = False 

918 permissions = ("elasticloadbalancing:DescribeLoadBalancerAttributes",) 

919 

920 def validate(self): 

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

922 return 

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

924 found = False 

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

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

927 found = True 

928 break 

929 if not found: 

930 raise PolicyValidationError( 

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

932 self.manager.data,)) 

933 return self 

934 

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

936 self.initialize(albs) 

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

938 

939 def __call__(self, alb): 

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

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

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

943 

944 found_listeners = False 

945 for listener in listeners: 

946 if self.match(listener): 

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

948 found_listeners = True 

949 return found_listeners 

950 

951 

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

953class AppELBModifyListenerPolicy(BaseAction): 

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

955 

956 :example: 

957 

958 .. code-block:: yaml 

959 

960 policies: 

961 - name: appelb-modify-listener 

962 resource: app-elb 

963 filters: 

964 - type: listener 

965 key: Protocol 

966 value: HTTP 

967 actions: 

968 - type: modify-listener 

969 protocol: HTTPS 

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

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

972 1234-1234-1234-123456789012" 

973 """ 

974 

975 schema = type_schema( 

976 'modify-listener', 

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

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

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

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

981 ) 

982 

983 permissions = ("elasticloadbalancing:ModifyListener",) 

984 

985 def validate(self): 

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

987 if f.type == 'listener': 

988 return self 

989 raise PolicyValidationError( 

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

991 self.manager.data,)) 

992 

993 def process(self, load_balancers): 

994 args = {} 

995 if 'port' in self.data: 

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

997 if 'protocol' in self.data: 

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

999 if 'sslpolicy' in self.data: 

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

1001 if 'certificate' in self.data: 

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

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

1004 

1005 for alb in load_balancers: 

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

1007 client.modify_listener( 

1008 ListenerArn=matched_listener['ListenerArn'], 

1009 **args) 

1010 

1011 

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

1013class AppELBModifyVpcSecurityGroups(ModifyVpcSecurityGroupsAction): 

1014 

1015 permissions = ("elasticloadbalancing:SetSecurityGroups",) 

1016 

1017 def process(self, albs): 

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

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

1020 

1021 for idx, i in enumerate(albs): 

1022 try: 

1023 client.set_security_groups( 

1024 LoadBalancerArn=i['LoadBalancerArn'], 

1025 SecurityGroups=groups[idx]) 

1026 except client.exceptions.LoadBalancerNotFoundException: 

1027 continue 

1028 

1029 

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

1031class AppELBHealthCheckProtocolMismatchFilter(Filter, 

1032 AppELBTargetGroupFilterBase): 

1033 """Filter AppELBs with mismatched health check protocols 

1034 

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

1036 does not match the load balancer health check protocol 

1037 

1038 :example: 

1039 

1040 .. code-block:: yaml 

1041 

1042 policies: 

1043 - name: appelb-healthcheck-mismatch 

1044 resource: app-elb 

1045 filters: 

1046 - healthcheck-protocol-mismatch 

1047 """ 

1048 

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

1050 permissions = ("elasticloadbalancing:DescribeTargetGroups",) 

1051 

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

1053 def _healthcheck_protocol_mismatch(alb): 

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

1055 if (target_group['Protocol'] != 

1056 target_group['HealthCheckProtocol']): 

1057 return True 

1058 

1059 return False 

1060 

1061 self.initialize(albs) 

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

1063 

1064 

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

1066class AppELBTargetGroupFilter(ValueFilter, AppELBTargetGroupFilterBase): 

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

1068 

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

1070 schema_alias = False 

1071 permissions = ("elasticloadbalancing:DescribeTargetGroups",) 

1072 

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

1074 self.initialize(albs) 

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

1076 

1077 def __call__(self, alb): 

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

1079 return self.match(target_groups) 

1080 

1081 

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

1083class AppELBDefaultVpcFilter(net_filters.DefaultVpcBase): 

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

1085 

1086 :example: 

1087 

1088 .. code-block:: yaml 

1089 

1090 policies: 

1091 - name: appelb-in-default-vpc 

1092 resource: app-elb 

1093 filters: 

1094 - default-vpc 

1095 """ 

1096 

1097 schema = type_schema('default-vpc') 

1098 

1099 def __call__(self, alb): 

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

1101 

1102 

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

1104class AppELBTargetGroup(QueryResourceManager): 

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

1106 """ 

1107 

1108 class resource_type(TypeInfo): 

1109 service = 'elbv2' 

1110 arn_type = 'target-group' 

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

1112 name = 'TargetGroupName' 

1113 id = 'TargetGroupArn' 

1114 permission_prefix = 'elasticloadbalancing' 

1115 cfn_type = 'AWS::ElasticLoadBalancingV2::TargetGroup' 

1116 

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

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

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

1120 

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

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

1123 

1124 @classmethod 

1125 def get_permissions(cls): 

1126 # override as the service is not the iam prefix 

1127 return ("elasticloadbalancing:DescribeTargetGroups", 

1128 "elasticloadbalancing:DescribeTags") 

1129 

1130 def augment(self, target_groups): 

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

1132 

1133 def _describe_target_group_health(target_group): 

1134 result = self.retry(client.describe_target_health, 

1135 TargetGroupArn=target_group['TargetGroupArn']) 

1136 target_group['TargetHealthDescriptions'] = result[ 

1137 'TargetHealthDescriptions'] 

1138 

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

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

1141 

1142 _describe_target_group_tags( 

1143 target_groups, self.session_factory, 

1144 self.executor_factory, self.retry) 

1145 return target_groups 

1146 

1147 

1148def _describe_target_group_tags(target_groups, session_factory, 

1149 executor_factory, retry): 

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

1151 

1152 def _process_tags(target_group_set): 

1153 target_group_map = { 

1154 target_group['TargetGroupArn']: 

1155 target_group for target_group in target_group_set 

1156 } 

1157 

1158 results = retry( 

1159 client.describe_tags, 

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

1161 for tag_desc in results['TagDescriptions']: 

1162 if ('ResourceArn' in tag_desc and 

1163 tag_desc['ResourceArn'] in target_group_map): 

1164 target_group_map[ 

1165 tag_desc['ResourceArn'] 

1166 ]['Tags'] = tag_desc['Tags'] 

1167 

1168 with executor_factory(max_workers=2) as w: 

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

1170 

1171 

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

1173class AppELBTargetGroupMarkForOpAction(tags.TagDelayedAction): 

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

1175 

1176 

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

1178class AppELBTargetGroupTagAction(tags.Tag): 

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

1180 

1181 :example: 

1182 

1183 .. code-block:: yaml 

1184 

1185 policies: 

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

1187 resource: app-elb-target-group 

1188 filters: 

1189 - "tag:RequiredTag": absent 

1190 actions: 

1191 - type: tag 

1192 key: RequiredTag 

1193 value: RequiredValue 

1194 """ 

1195 

1196 batch_size = 1 

1197 permissions = ("elasticloadbalancing:AddTags",) 

1198 

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

1200 client.add_tags( 

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

1202 Tags=ts) 

1203 

1204 

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

1206class AppELBTargetGroupRemoveTagAction(tags.RemoveTag): 

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

1208 

1209 :example: 

1210 

1211 .. code-block:: yaml 

1212 

1213 policies: 

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

1215 resource: app-elb-target-group 

1216 filters: 

1217 - "tag:ExpiredTag": present 

1218 actions: 

1219 - type: remove-tag 

1220 tags: ["ExpiredTag"] 

1221 """ 

1222 

1223 batch_size = 1 

1224 permissions = ("elasticloadbalancing:RemoveTags",) 

1225 

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

1227 client.remove_tags( 

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

1229 TagKeys=tag_keys) 

1230 

1231 

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

1233class AppELBTargetGroupDefaultVpcFilter(net_filters.DefaultVpcBase): 

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

1235 

1236 :example: 

1237 

1238 .. code-block:: yaml 

1239 

1240 policies: 

1241 - name: appelb-targetgroups-default-vpc 

1242 resource: app-elb-target-group 

1243 filters: 

1244 - default-vpc 

1245 """ 

1246 

1247 schema = type_schema('default-vpc') 

1248 

1249 def __call__(self, target_group): 

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

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

1252 

1253 

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

1255class AppELBTargetGroupDeleteAction(BaseAction): 

1256 """Action to delete ELB target group 

1257 

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

1259 deletion of any app elb target groups. 

1260 

1261 :example: 

1262 

1263 .. code-block:: yaml 

1264 

1265 policies: 

1266 - name: appelb-targetgroups-delete-unused 

1267 resource: app-elb-target-group 

1268 filters: 

1269 - "tag:SomeTag": absent 

1270 actions: 

1271 - delete 

1272 """ 

1273 

1274 schema = type_schema('delete') 

1275 permissions = ('elasticloadbalancing:DeleteTargetGroup',) 

1276 

1277 def process(self, resources): 

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

1279 for tg in resources: 

1280 self.process_target_group(client, tg) 

1281 

1282 def process_target_group(self, client, target_group): 

1283 self.manager.retry( 

1284 client.delete_target_group, 

1285 TargetGroupArn=target_group['TargetGroupArn']) 

1286 

1287 

1288class TargetGroupAttributeFilterBase: 

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

1290 """ 

1291 

1292 def initialize(self, tgs): 

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

1294 

1295 def _process_attributes(tg): 

1296 if 'c7n:TargetGroupAttributes' not in tg: 

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

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

1299 TargetGroupArn=tg['TargetGroupArn'], 

1300 ignore_err_codes=('TargetGroupNotFoundException',)) 

1301 # flatten out the list of dicts and cast 

1302 for pair in results['Attributes']: 

1303 k = pair['Key'] 

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

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

1306 

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

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

1309 

1310 

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

1312class TargetGroupCheckAttributes(ValueFilter, TargetGroupAttributeFilterBase): 

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

1314 

1315 :example: 

1316 

1317 .. code-block:: yaml 

1318 

1319 policies: 

1320 - name: target-group-check-attributes 

1321 resource: app-elb-target-group 

1322 filters: 

1323 - type: attributes 

1324 key: preserve_client_ip.enabled 

1325 value: True 

1326 op: eq 

1327 """ 

1328 annotate: False # no annotation from value Filter 

1329 permissions = ("elasticloadbalancing:DescribeTargetGroupAttributes",) 

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

1331 schema_alias = False 

1332 

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

1334 self.augment(resources) 

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

1336 

1337 def augment(self, resources): 

1338 self.initialize(resources) 

1339 

1340 def __call__(self, r): 

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

1342 

1343 

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

1345class AppELBTargetGroupModifyAttributes(BaseAction): 

1346 """Modify target group attributes. 

1347 

1348 :example: 

1349 

1350 .. code-block:: yaml 

1351 

1352 policies: 

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

1354 resource: app-elb-target-group 

1355 filters: 

1356 - type: attributes 

1357 key: "preserve_client_ip.enabled" 

1358 value: False 

1359 actions: 

1360 - type: modify-attributes 

1361 attributes: 

1362 "preserve_client_ip.enabled": "true" 

1363 """ 

1364 schema = { 

1365 'type': 'object', 

1366 'additionalProperties': False, 

1367 'properties': { 

1368 'type': { 

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

1370 'attributes': { 

1371 'type': 'object', 

1372 'additionalProperties': False, 

1373 'properties': { 

1374 'proxy_protocol_v2.enabled': { 

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

1376 'preserve_client_ip.enabled': { 

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

1378 'stickiness.enabled': { 

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

1380 'lambda.multi_value_headers.enabled': { 

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

1382 'deregistration_delay.connection_termination.enabled': { 

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

1384 'target_group_health.unhealthy_state_routing.' 

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

1386 'target_group_health.unhealthy_state_routing.' 

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

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

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

1390 'type': 'string'}, 

1391 'stickiness.type': { 

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

1393 'source_ip_dest_ip', 'source_ip_dest_ip_proto']}, 

1394 'load_balancing.cross_zone.enabled': { 

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

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

1397 'type': 'string'}, 

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

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

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

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

1402 'load_balancing.algorithm.type': { 

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

1404 'target_failover.on_deregistration': { 

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

1406 'target_failover.on_unhealthy': { 

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

1408 }, 

1409 }, 

1410 }, 

1411 } 

1412 permissions = ("elasticloadbalancing:ModifyTargetGroupAttributes",) 

1413 

1414 def process(self, resources): 

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

1416 self.log.info(resources) 

1417 for targetgroup in resources: 

1418 self.manager.retry( 

1419 client.modify_target_group_attributes, 

1420 TargetGroupArn=targetgroup['TargetGroupArn'], 

1421 Attributes=[ 

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

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

1424 ], 

1425 ignore_err_codes=('TargetGroupNotFoundException',), 

1426 ) 

1427 return resources