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
« 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
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
25from c7n.query import QueryResourceManager, DescribeSource, ConfigSource, TypeInfo
26from c7n.utils import (
27 local_session, chunks, type_schema, get_retry, set_annotation)
29from c7n.resources.aws import Arn
30from c7n.resources.shield import IsShieldProtected, SetShieldProtection
32log = logging.getLogger('custodian.app-elb')
35class DescribeAppElb(DescribeSource):
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)
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)
53 return albs
56class ConfigAppElb(ConfigSource):
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
71@resources.register('app-elb')
72class AppELB(QueryResourceManager):
73 """Resource manager for v2 ELBs (AKA ALBs and NLBs).
74 """
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'
91 retry = staticmethod(get_retry(('Throttling',)))
92 source_mapping = {
93 'describe': DescribeAppElb,
94 'config': ConfigAppElb
95 }
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")
105def _describe_appelb_tags(albs, session_factory, executor_factory, retry):
106 client = local_session(session_factory).client('elbv2')
108 def _process_tags(alb_set):
109 alb_map = {alb['LoadBalancerArn']: alb for alb in alb_set}
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']
117 with executor_factory(max_workers=2) as w:
118 list(w.map(_process_tags, chunks(albs, 20)))
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)
128@AppELB.filter_registry.register('metrics')
129class AppElbMetrics(MetricsFilter):
130 """Filter app/net load balancer by metric values.
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`
138 See available application load balancer metrics here
139 https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-cloudwatch-metrics.html
141 See available network load balancer metrics here.
142 https://docs.aws.amazon.com/elasticloadbalancing/latest/network/load-balancer-cloudwatch-metrics.html
145 For network load balancer metrics, the metrics filter requires specifying
146 the namespace parameter to the filter.
148 .. code-block:: yaml
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 """
164 def get_dimensions(self, resource):
165 return [{
166 'Name': self.model.dimension,
167 'Value': Arn.parse(resource['LoadBalancerArn']).resource}]
170@AppELB.filter_registry.register('security-group')
171class SecurityGroupFilter(net_filters.SecurityGroupFilter):
173 RelatedIdsExpression = "SecurityGroups[]"
176@AppELB.filter_registry.register('subnet')
177class SubnetFilter(net_filters.SubnetFilter):
179 RelatedIdsExpression = "AvailabilityZones[].SubnetId"
182@AppELB.filter_registry.register('vpc')
183class VpcFilter(net_filters.VpcFilter):
185 RelatedIdsExpression = "VpcId"
188@AppELB.filter_registry.register('waf-enabled')
189class WafEnabled(WafClassicRegionalFilterBase):
190 """Filter Application LoadBalancer by waf-regional web-acl
192 :example:
194 .. code-block:: yaml
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 """
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 )
214@AppELB.filter_registry.register('wafv2-enabled')
215class WafV2Enabled(WafV2FilterBase):
216 """Filter Application LoadBalancer by wafv2 web-acl
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.
222 :example:
224 .. code-block:: yaml
226 policies:
227 - name: filter-wafv2-elb
228 resource: app-elb
229 filters:
230 - type: wafv2-enabled
231 state: false
232 web-acl: testv2
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 """
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 )
251@AppELB.action_registry.register('set-waf')
252class SetWaf(BaseAction):
253 """Enable wafv2 protection on Application LoadBalancer.
255 :example:
257 .. code-block:: yaml
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
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
281 """
282 permissions = ('waf-regional:AssociateWebACL', 'waf-regional:ListWebACLs')
284 schema = type_schema(
285 'set-waf', required=['web-acl'], **{
286 'web-acl': {'type': 'string'},
287 # 'force': {'type': 'boolean'},
288 'state': {'type': 'boolean'}})
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
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)
310 if state and target_acl_id not in name_id_map.values():
311 raise ValueError("invalid web acl: %s" % (target_acl_id))
313 client = local_session(
314 self.manager.session_factory).client('waf-regional')
316 arn_key = self.manager.resource_type.id
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])
329@AppELB.action_registry.register('set-wafv2')
330class SetWafV2(BaseAction):
331 """Enable wafv2 protection on Application LoadBalancer.
333 Supports regex expression for web-acl
335 :example:
337 .. code-block:: yaml
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
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
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
372 """
373 permissions = ('wafv2:AssociateWebACL',
374 'wafv2:DisassociateWebACL',
375 'wafv2:ListWebACLs')
377 schema = type_schema(
378 'set-wafv2', **{
379 'web-acl': {'type': 'string'},
380 'state': {'type': 'boolean'}})
382 retry = staticmethod(get_retry((
383 'ThrottlingException',
384 'RequestLimitExceeded',
385 'Throttled',
386 'ThrottledException',
387 'Throttling',
388 'Client.RequestLimitExceeded')))
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
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)
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]
418 client = local_session(
419 self.manager.session_factory).client('wafv2')
421 arn_key = self.manager.resource_type.id
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])
435@AppELB.action_registry.register('set-s3-logging')
436class SetS3Logging(BaseAction):
437 """Action to enable/disable S3 logging for an application loadbalancer.
439 :example:
441 .. code-block:: yaml
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',))
461 permissions = ("elasticloadbalancing:ModifyLoadBalancerAttributes",)
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
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')}]
480 if self.data.get('state') == 'enabled':
481 attributes.append({
482 'Key': 'access_logs.s3.bucket',
483 'Value': self.data['bucket']})
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']
491 attributes.append({
492 'Key': 'access_logs.s3.prefix',
493 'Value': prefix_template.format(**info)})
495 self.manager.retry(
496 client.modify_load_balancer_attributes,
497 LoadBalancerArn=elb_arn, Attributes=attributes)
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
504 :example:
506 .. code-block:: yaml
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 """
522 batch_size = 1
525@AppELB.action_registry.register('tag')
526class AppELBTagAction(tags.Tag):
527 """Action to create tag/tags on an ELB
529 :example:
531 .. code-block:: yaml
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 """
544 batch_size = 1
545 permissions = ("elasticloadbalancing:AddTags",)
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)
553@AppELB.action_registry.register('remove-tag')
554class AppELBRemoveTagAction(tags.RemoveTag):
555 """Action to remove tag/tags from an ELB
557 :example:
559 .. code-block:: yaml
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 """
571 batch_size = 1
572 permissions = ("elasticloadbalancing:RemoveTags",)
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)
580@AppELB.action_registry.register('delete')
581class AppELBDeleteAction(BaseAction):
582 """Action to delete an ELB
584 To avoid unwanted deletions of ELB, it is recommended to apply a filter
585 to the rule
587 :example:
589 .. code-block:: yaml
591 policies:
592 - name: appelb-delete-failed-elb
593 resource: app-elb
594 filters:
595 - State: failed
596 actions:
597 - delete
598 """
600 schema = type_schema('delete', force={'type': 'boolean'})
601 permissions = (
602 "elasticloadbalancing:DeleteLoadBalancer",
603 "elasticloadbalancing:ModifyLoadBalancerAttributes",)
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)
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)
632@AppELB.action_registry.register('modify-attributes')
633class AppELBModifyAttributes(BaseAction):
634 """Modify load balancer attributes.
636 :example:
638 .. code-block:: yaml
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",)
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
699class AppELBListenerFilterBase:
700 """ Mixin base class for filters that query LB listeners.
701 """
702 permissions = ("elasticloadbalancing:DescribeListeners",)
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']
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
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
734class AppELBAttributeFilterBase:
735 """ Mixin base class for filters that query LB attributes.
736 """
738 def initialize(self, albs):
739 client = local_session(self.manager.session_factory).client('elbv2')
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
752 with self.manager.executor_factory(max_workers=2) as w:
753 list(w.map(_process_attributes, albs))
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
761 :example:
763 .. code-block:: yaml
765 policies:
766 - name: alb-is-logging-test
767 resource: app-elb
768 filters:
769 - type: is-logging
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
778 """
779 permissions = ("elasticloadbalancing:DescribeLoadBalancerAttributes",)
780 schema = type_schema('is-logging',
781 bucket={'type': 'string'},
782 prefix={'type': 'string'}
783 )
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)
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 ]
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.
804 :example:
806 .. code-block:: yaml
808 policies:
809 - name: alb-is-not-logging-test
810 resource: app-elb
811 filters:
812 - type: is-not-logging
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
821 """
822 permissions = ("elasticloadbalancing:DescribeLoadBalancerAttributes",)
823 schema = type_schema('is-not-logging',
824 bucket={'type': 'string'},
825 prefix={'type': 'string'}
826 )
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)
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))]
841@AppELB.filter_registry.register('attributes')
842class CheckAttributes(ValueFilter, AppELBAttributeFilterBase):
843 """ Value filter that allows filtering on ELBv2 attributes
845 :example:
847 .. code-block:: yaml
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
863 def process(self, resources, event=None):
864 self.augment(resources)
865 return super().process(resources, event)
867 def augment(self, resources):
868 self.initialize(resources)
870 def __call__(self, r):
871 return super().__call__(r['Attributes'])
874class AppELBTargetGroupFilterBase:
875 """ Mixin base class for filters that query LB target groups.
876 """
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)
887@AppELB.filter_registry.register('listener')
888class AppELBListenerFilter(ValueFilter, AppELBListenerFilterBase):
889 """Filter ALB based on matching listener attributes
891 Adding the `matched` flag will filter on previously matched listeners
893 :example:
895 .. code-block:: yaml
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 """
914 schema = type_schema(
915 'listener', rinherit=ValueFilter.schema, matched={'type': 'boolean'})
916 schema_alias = False
917 permissions = ("elasticloadbalancing:DescribeLoadBalancerAttributes",)
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
934 def process(self, albs, event=None):
935 self.initialize(albs)
936 return super(AppELBListenerFilter, self).process(albs, event)
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', [])
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
951@AppELB.action_registry.register('modify-listener')
952class AppELBModifyListenerPolicy(BaseAction):
953 """Action to modify the policy for an App ELB
955 :example:
957 .. code-block:: yaml
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 """
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 )
982 permissions = ("elasticloadbalancing:ModifyListener",)
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,))
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')
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)
1011@AppELB.action_registry.register('modify-security-groups')
1012class AppELBModifyVpcSecurityGroups(ModifyVpcSecurityGroupsAction):
1014 permissions = ("elasticloadbalancing:SetSecurityGroups",)
1016 def process(self, albs):
1017 client = local_session(self.manager.session_factory).client('elbv2')
1018 groups = super(AppELBModifyVpcSecurityGroups, self).get_groups(albs)
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
1029@AppELB.filter_registry.register('healthcheck-protocol-mismatch')
1030class AppELBHealthCheckProtocolMismatchFilter(Filter,
1031 AppELBTargetGroupFilterBase):
1032 """Filter AppELBs with mismatched health check protocols
1034 A mismatched health check protocol is where the protocol on the target group
1035 does not match the load balancer health check protocol
1037 :example:
1039 .. code-block:: yaml
1041 policies:
1042 - name: appelb-healthcheck-mismatch
1043 resource: app-elb
1044 filters:
1045 - healthcheck-protocol-mismatch
1046 """
1048 schema = type_schema('healthcheck-protocol-mismatch')
1049 permissions = ("elasticloadbalancing:DescribeTargetGroups",)
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
1058 return False
1060 self.initialize(albs)
1061 return [alb for alb in albs if _healthcheck_protocol_mismatch(alb)]
1064@AppELB.filter_registry.register('target-group')
1065class AppELBTargetGroupFilter(ValueFilter, AppELBTargetGroupFilterBase):
1066 """Filter ALB based on matching target group value"""
1068 schema = type_schema('target-group', rinherit=ValueFilter.schema)
1069 schema_alias = False
1070 permissions = ("elasticloadbalancing:DescribeTargetGroups",)
1072 def process(self, albs, event=None):
1073 self.initialize(albs)
1074 return super(AppELBTargetGroupFilter, self).process(albs, event)
1076 def __call__(self, alb):
1077 target_groups = self.target_group_map[alb['LoadBalancerArn']]
1078 return self.match(target_groups)
1081@AppELB.filter_registry.register('default-vpc')
1082class AppELBDefaultVpcFilter(net_filters.DefaultVpcBase):
1083 """Filter all ELB that exist within the default vpc
1085 :example:
1087 .. code-block:: yaml
1089 policies:
1090 - name: appelb-in-default-vpc
1091 resource: app-elb
1092 filters:
1093 - default-vpc
1094 """
1096 schema = type_schema('default-vpc')
1098 def __call__(self, alb):
1099 return alb.get('VpcId') and self.match(alb.get('VpcId')) or False
1102@resources.register('app-elb-target-group')
1103class AppELBTargetGroup(QueryResourceManager):
1104 """Resource manager for v2 ELB target groups.
1105 """
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'
1116 filter_registry = FilterRegistry('app-elb-target-group.filters')
1117 action_registry = ActionRegistry('app-elb-target-group.actions')
1118 retry = staticmethod(get_retry(('Throttling',)))
1120 filter_registry.register('tag-count', tags.TagCountFilter)
1121 filter_registry.register('marked-for-op', tags.TagActionFilter)
1123 @classmethod
1124 def get_permissions(cls):
1125 # override as the service is not the iam prefix
1126 return ("elasticloadbalancing:DescribeTargetGroups",
1127 "elasticloadbalancing:DescribeTags")
1129 def augment(self, target_groups):
1130 client = local_session(self.session_factory).client('elbv2')
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']
1138 with self.executor_factory(max_workers=2) as w:
1139 list(w.map(_describe_target_group_health, target_groups))
1141 _describe_target_group_tags(
1142 target_groups, self.session_factory,
1143 self.executor_factory, self.retry)
1144 return target_groups
1147def _describe_target_group_tags(target_groups, session_factory,
1148 executor_factory, retry):
1149 client = local_session(session_factory).client('elbv2')
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 }
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']
1167 with executor_factory(max_workers=2) as w:
1168 list(w.map(_process_tags, chunks(target_groups, 20)))
1171@AppELBTargetGroup.action_registry.register('mark-for-op')
1172class AppELBTargetGroupMarkForOpAction(tags.TagDelayedAction):
1173 """Action to specify a delayed action on an ELB target group"""
1176@AppELBTargetGroup.action_registry.register('tag')
1177class AppELBTargetGroupTagAction(tags.Tag):
1178 """Action to create tag/tags on an ELB target group
1180 :example:
1182 .. code-block:: yaml
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 """
1195 batch_size = 1
1196 permissions = ("elasticloadbalancing:AddTags",)
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)
1204@AppELBTargetGroup.action_registry.register('remove-tag')
1205class AppELBTargetGroupRemoveTagAction(tags.RemoveTag):
1206 """Action to remove tag/tags from ELB target group
1208 :example:
1210 .. code-block:: yaml
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 """
1222 batch_size = 1
1223 permissions = ("elasticloadbalancing:RemoveTags",)
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)
1231@AppELBTargetGroup.filter_registry.register('default-vpc')
1232class AppELBTargetGroupDefaultVpcFilter(net_filters.DefaultVpcBase):
1233 """Filter all application elb target groups within the default vpc
1235 :example:
1237 .. code-block:: yaml
1239 policies:
1240 - name: appelb-targetgroups-default-vpc
1241 resource: app-elb-target-group
1242 filters:
1243 - default-vpc
1244 """
1246 schema = type_schema('default-vpc')
1248 def __call__(self, target_group):
1249 return (target_group.get('VpcId') and
1250 self.match(target_group.get('VpcId')) or False)
1253@AppELBTargetGroup.action_registry.register('delete')
1254class AppELBTargetGroupDeleteAction(BaseAction):
1255 """Action to delete ELB target group
1257 It is recommended to apply a filter to the delete policy to avoid unwanted
1258 deletion of any app elb target groups.
1260 :example:
1262 .. code-block:: yaml
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 """
1273 schema = type_schema('delete')
1274 permissions = ('elasticloadbalancing:DeleteTargetGroup',)
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)
1281 def process_target_group(self, client, target_group):
1282 self.manager.retry(
1283 client.delete_target_group,
1284 TargetGroupArn=target_group['TargetGroupArn'])
1287class TargetGroupAttributeFilterBase:
1288 """ Mixin base class for filters that query Target Group attributes.
1289 """
1291 def initialize(self, tgs):
1292 client = local_session(self.manager.session_factory).client('elbv2')
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
1306 with self.manager.executor_factory(max_workers=2) as w:
1307 list(w.map(_process_attributes, tgs))
1310@AppELBTargetGroup.filter_registry.register('attributes')
1311class TargetGroupCheckAttributes(ValueFilter, TargetGroupAttributeFilterBase):
1312 """ Value filter that allows filtering on Target group attributes
1314 :example:
1316 .. code-block:: yaml
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
1332 def process(self, resources, event=None):
1333 self.augment(resources)
1334 return super().process(resources, event)
1336 def augment(self, resources):
1337 self.initialize(resources)
1339 def __call__(self, r):
1340 return super().__call__(r['c7n:TargetGroupAttributes'])
1343@AppELBTargetGroup.action_registry.register('modify-attributes')
1344class AppELBTargetGroupModifyAttributes(BaseAction):
1345 """Modify target group attributes.
1347 :example:
1349 .. code-block:: yaml
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",)
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