class EBSSnapshotResourceSpec(EC2ResourceSpec): """Resource for EBSSnapshots""" type_name = "snapshot" schema = Schema( ScalarField("VolumeSize"), ScalarField("Encrypted"), TransientResourceLinkField("KmsKeyId", KMSKeyResourceSpec, optional=True, value_is_id=True), TransientResourceLinkField("VolumeId", EBSVolumeResourceSpec), TagsField(), ) @classmethod def list_from_aws(cls: Type["EBSSnapshotResourceSpec"], client: BaseClient, account_id: str, region: str) -> ListFromAWSResult: """Return a dict of dicts of the format: {'snapshot_1_arn': {snapshot_1_dict}, 'snapshot_2_arn': {snapshot_2_dict}, ...} Where the dicts represent results from describe_snapshots.""" snapshots = {} paginator = client.get_paginator("describe_snapshots") for resp in paginator.paginate(OwnerIds=["self"]): for snapshot in resp.get("Snapshots", []): resource_arn = cls.generate_arn( account_id=account_id, region=region, resource_id=snapshot["SnapshotId"]) snapshots[resource_arn] = snapshot return ListFromAWSResult(resources=snapshots)
class EC2InstanceResourceSpec(EC2ResourceSpec): """Resource for EC2Instances""" type_name = "instance" schema = Schema( ScalarField("Name", optional=True), TransientResourceLinkField("ImageId", EC2ImageResourceSpec), ScalarField("KeyName", optional=True), AnonymousDictField("Placement", ScalarField("AvailabilityZone"), ScalarField("Tenancy")), ScalarField("InstanceType"), ScalarField("LaunchTime"), AnonymousDictField("State", ScalarField("Name", "state")), ScalarField("Platform", optional=True), ScalarField("PrivateIpAddress", optional=True), ScalarField("PrivateDnsName", optional=True), ScalarField("PublicIpAddress", optional=True), ScalarField("PublicDnsName", optional=True), ResourceLinkField("VpcId", VPCResourceSpec, optional=True), ResourceLinkField("SubnetId", SubnetResourceSpec, optional=True), AnonymousListField( "SecurityGroups", AnonymousEmbeddedDictField(ResourceLinkField("GroupId", SecurityGroupResourceSpec)), ), AnonymousDictField( "IamInstanceProfile", TransientResourceLinkField( "Arn", InstanceProfileResourceSpec, alti_key="instance_profile", value_is_id=True ), optional=True, ), TagsField(), ) @classmethod def list_from_aws( cls: Type["EC2InstanceResourceSpec"], client: BaseClient, account_id: str, region: str ) -> ListFromAWSResult: """Return a dict of dicts of the format: {'instance_1_arn': {instance_1_dict}, 'instance_2_arn': {instance_2_dict}, ...} Where the dicts represent results from describe_instances.""" paginator = client.get_paginator("describe_instances") instances = {} for resp in paginator.paginate(): for reservation in resp.get("Reservations", []): for instance in reservation.get("Instances", []): resource_arn = cls.generate_arn( account_id=account_id, region=region, resource_id=instance["InstanceId"] ) instances[resource_arn] = instance for tag in instance.get("Tags", []): if tag["Key"].lower() == "name": instance["Name"] = tag["Value"] break return ListFromAWSResult(resources=instances)
class RDSSnapshotResourceSpec(RDSResourceSpec): """Resource for RDS Snapshot""" type_name = "snapshot" schema = Schema( ScalarField("DBSnapshotIdentifier"), ScalarField("SnapshotCreateTime", optional=True), ScalarField("Engine"), ScalarField("AllocatedStorage"), ScalarField("Status"), TransientResourceLinkField("VpcId", VPCResourceSpec, optional=True), ScalarField("InstanceCreateTime"), ScalarField("SnapshotType"), ScalarField("PercentProgress"), ScalarField("Region", optional=True), ScalarField("Encrypted"), TransientResourceLinkField("KmsKeyID", KMSKeyResourceSpec, optional=True), ScalarField("Timezone", optional=True), ScalarField("IAMDatabaseAuthenticationEnabled"), TransientResourceLinkField("DBInstanceArn", RDSInstanceResourceSpec, value_is_id=True), ) @classmethod def generate_instance_arn( cls: Type["RDSSnapshotResourceSpec"], account_id: str, region: str, resource_id: str ) -> str: """Generate an ARN for this resource, e.g. arn:aws:rds:<region>:<account>:db:<name> """ return ":".join( ( "arn", cls.provider_name, cls.service_name, region, account_id, cls.type_name, resource_id, ) ) @classmethod def list_from_aws( cls: Type["RDSSnapshotResourceSpec"], client: BaseClient, account_id: str, region: str ) -> ListFromAWSResult: dbinstances = {} paginator = client.get_paginator("describe_db_snapshots") for resp in paginator.paginate(): for db in resp.get("DBSnapshots", []): resource_arn = db["DBSnapshotArn"] db["DBInstanceArn"] = cls.generate_instance_arn( account_id, region, db["DBInstanceIdentifier"] ) dbinstances[resource_arn] = db return ListFromAWSResult(resources=dbinstances)
class LambdaFunctionResourceSpec(LambdaResourceSpec): """Resource for Lambda Functions""" type_name = "function" schema = Schema( ScalarField("FunctionName"), ScalarField("Runtime", optional=True), AnonymousDictField( "VpcConfig", TransientResourceLinkField("VpcId", VPCResourceSpec, optional=True), optional=True, ), ) @classmethod def list_from_aws(cls: Type["LambdaFunctionResourceSpec"], client: BaseClient, account_id: str, region: str) -> ListFromAWSResult: """Return a dict of dicts of the format: {'function_1_arn': {function_1_dict}, 'function_2_arn': {function_2_dict}, ...} Where the dicts represent results from list_functions.""" functions = {} paginator = client.get_paginator("list_functions") for resp in paginator.paginate(): for function in resp.get("Functions", []): resource_arn = function["FunctionArn"] functions[resource_arn] = function return ListFromAWSResult(resources=functions)
class TargetGroupResourceSpec(ElasticLoadBalancingResourceSpec): """Resource for target group""" type_name = "targetgroup" schema = Schema( ScalarField("TargetGroupName"), ScalarField("Protocol", optional=True), ScalarField("Port", optional=True), TransientResourceLinkField("VpcId", VPCResourceSpec, optional=True), ScalarField("HealthCheckProtocol", optional=True), ScalarField("HealthCheckPort", optional=True), ScalarField("HealthCheckEnabled"), ListField( "LoadBalancerArns", EmbeddedResourceLinkField(LoadBalancerResourceSpec, value_is_id=True), ), ScalarField("TargetType"), ListField( "TargetHealthDescriptions", EmbeddedDictField( AnonymousDictField( "Target", ScalarField("Id", alti_key="target_id"), ScalarField("Port", alti_key="target_port", optional=True), ScalarField("AvailabilityZone", alti_key="target_az", optional=True), ), ScalarField("HealthCheckPort", optional=True), AnonymousDictField( "TargetHealth", ScalarField("State"), ScalarField("Reason", optional=True), ScalarField("Description", optional=True), optional=True, ), ), ), ) @classmethod def list_from_aws(cls: Type["TargetGroupResourceSpec"], client: BaseClient, account_id: str, region: str) -> ListFromAWSResult: """Return a dict of dicts of the format: {'target_group_1_arn': {target_group_1_dict}, 'target_group_2_arn': {target_group_2_dict}, ...} Where the dicts represent results from describe_target_groups.""" paginator = client.get_paginator("describe_target_groups") resources = {} for resp in paginator.paginate(): for resource in resp.get("TargetGroups", []): resource_arn = resource["TargetGroupArn"] resource["TargetHealthDescriptions"] = get_target_group_health( client, resource_arn) resources[resource_arn] = resource return ListFromAWSResult(resources=resources)
class EBSVolumeResourceSpec(EC2ResourceSpec): """Resource for EBSVolumes""" type_name = "volume" schema = Schema( ScalarField("AvailabilityZone"), ScalarField("CreateTime"), ScalarField("Size"), ScalarField("State"), ScalarField("VolumeType"), ScalarField("Encrypted"), ListField( "Attachments", EmbeddedDictField( ScalarField("AttachTime"), ScalarField("State"), ScalarField("DeleteOnTermination"), ResourceLinkField("InstanceId", EC2InstanceResourceSpec), ), optional=True, alti_key="attachment", ), TransientResourceLinkField("KmsKeyId", KMSKeyResourceSpec, optional=True, value_is_id=True), TagsField(), ) @classmethod def list_from_aws(cls: Type["EBSVolumeResourceSpec"], client: BaseClient, account_id: str, region: str) -> ListFromAWSResult: """Return a dict of dicts of the format: {'volume_1_arn': {volume_1_dict}, 'volume_2_arn': {volume_2_dict}, ...} Where the dicts represent results from describe_volumes.""" volumes = {} paginator = client.get_paginator("describe_volumes") for resp in paginator.paginate(): for volume in resp.get("Volumes", []): resource_arn = cls.generate_arn(account_id=account_id, region=region, resource_id=volume["VolumeId"]) volumes[resource_arn] = volume return ListFromAWSResult(resources=volumes)
class FlowLogResourceSpec(EC2ResourceSpec): """Resource for VPC Flow Logs""" # type_name = "flow-log" type_name = "flow_log" schema = Schema( ScalarField("CreationTime"), ScalarField("DeliverLogsErrorMessage", optional=True), ScalarField("DeliverLogsPermissionArn", optional=True), ScalarField("DeliverLogsStatus", optional=True), ScalarField("FlowLogStatus"), ScalarField("LogGroupName", optional=True), TransientResourceLinkField("ResourceId", VPCResourceSpec, optional=True), ScalarField("TrafficType"), ScalarField("LogDestinationType"), ScalarField("LogDestination", optional=True), ScalarField("LogFormat"), ) @classmethod def list_from_aws(cls: Type["FlowLogResourceSpec"], client: BaseClient, account_id: str, region: str) -> ListFromAWSResult: """Return a dict of dicts of the format: {'fl_1_arn': {fl_1_dict}, 'fl_2_arn': {fl_2_dict}, ...} Where the dicts represent results from describe_flow_logs.""" flow_logs = {} paginator = client.get_paginator("describe_flow_logs") for resp in paginator.paginate(): for flow_log in resp.get("FlowLogs", []): resource_arn = cls.generate_arn( account_id=account_id, region=region, resource_id=flow_log["FlowLogId"]) flow_logs[resource_arn] = flow_log return ListFromAWSResult(resources=flow_logs)
class RDSInstanceResourceSpec(RDSResourceSpec): """Resource for RDS""" type_name = "db" schema = Schema( TagsField(), ScalarField("DBInstanceIdentifier"), ScalarField("DBInstanceClass"), ScalarField("Engine"), ScalarField("DBInstanceStatus"), ScalarField("DBName", optional=True), AnonymousDictField( "Endpoint", ScalarField("Address", alti_key="endpoint_address", optional=True), ScalarField("Port", alti_key="endpoint_port"), ScalarField("HostedZoneId", alti_key="endpoint_hosted_zone", optional=True), optional=True, ), AnonymousDictField( "ListenerEndpoint", ScalarField("Address", alti_key="listener_address"), ScalarField("Port", alti_key="listener_port"), ScalarField("HostedZoneId", alti_key="listener_hosted_zone", optional=True), optional=True, ), ScalarField("InstanceCreateTime", optional=True), ScalarField("BackupRetentionPeriod"), AnonymousListField( "VpcSecurityGroups", AnonymousEmbeddedDictField( TransientResourceLinkField("VpcSecurityGroupId", SecurityGroupResourceSpec, optional=True)), ), ScalarField("AvailabilityZone", optional=True), AnonymousDictField("DBSubnetGroup", TransientResourceLinkField("VpcId", VPCResourceSpec), optional=True), ScalarField("MultiAZ"), ScalarField("PubliclyAccessible"), ListField("StatusInfos", EmbeddedDictField(ScalarField("Status")), optional=True), ScalarField("StorageType"), ScalarField("StorageEncrypted"), TransientResourceLinkField("KmsKeyId", KMSKeyResourceSpec, optional=True, value_is_id=True), ScalarField("DbiResourceId"), ScalarField("Timezone", optional=True), ScalarField("IAMDatabaseAuthenticationEnabled"), ScalarField("PerformanceInsightsEnabled", optional=True), ScalarField("PerformanceInsightsRetentionPeriod", optional=True), ScalarField("DeletionProtection"), ListField( "Backup", EmbeddedDictField( AnonymousDictField( "RestoreWindow", ScalarField("EarliestTime", alti_key="earliest_restore_time", optional=True), optional=True, ), AnonymousDictField( "RestoreWindow", ScalarField("LatestTime", alti_key="latest_restore_time", optional=True), optional=True, ), ScalarField("AllocatedStorage"), ScalarField("Status"), ScalarField("AvailabilityZone", optional=True), ScalarField("Engine"), ScalarField("EngineVersion"), ScalarField("Encrypted"), ScalarField("StorageType"), TransientResourceLinkField("KmsKeyId", KMSKeyResourceSpec, optional=True, value_is_id=True), ), optional=True, ), ) @classmethod def list_from_aws(cls: Type["RDSInstanceResourceSpec"], client: BaseClient, account_id: str, region: str) -> ListFromAWSResult: logger = Logger() dbinstances = {} paginator = client.get_paginator("describe_db_instances") for resp in paginator.paginate(): for db in resp.get("DBInstances", []): resource_arn = db["DBInstanceArn"] db["Tags"] = client.list_tags_for_resource( ResourceName=resource_arn).get("TagList", []) db["Backup"] = [] dbinstances[resource_arn] = db backup_paginator = client.get_paginator( "describe_db_instance_automated_backups") for resp in backup_paginator.paginate(): for backup in resp.get("DBInstanceAutomatedBackups", []): if backup["DBInstanceArn"] in dbinstances: dbinstances[backup["DBInstanceArn"]]["Backup"].append( backup) else: logger.info( event=AWSLogEvents.ScanAWSResourcesNonFatalError, msg= (f'Unable to find matching DB Instance {backup["DBInstanceArn"]} ' "(Possible Deletion)"), ) return ListFromAWSResult(resources=dbinstances)
class VPCPeeringConnectionResourceSpec(EC2ResourceSpec): """Resource for VPC Peering Connections.""" # type_name = "vpc-peering-connection" type_name = "vpc_peering_connection" schema = Schema( TransientResourceLinkField("AccepterVpc", VPCResourceSpec, value_is_id=True, alti_key="accepter_vpc"), TransientResourceLinkField("RequesterVpc", VPCResourceSpec, value_is_id=True, alti_key="requester_vpc"), ScalarField("Status"), TagsField(), ) @classmethod def list_from_aws( cls: Type["VPCPeeringConnectionResourceSpec"], client: BaseClient, account_id: str, region: str, ) -> ListFromAWSResult: """Return a dict of dicts of the format: {'vpc_peering_connection_1_arn': {vpc_peering_connection_1_dict}, 'vpc_peering_connection_2_arn': {vpc_peering_connection_2_dict}, ...} Where the dicts represent results from describe_vpc_peering_connections.""" vpc_peering_connections = {} paginator = client.get_paginator("describe_vpc_peering_connections") for resp in paginator.paginate(): for vpc_pc in resp["VpcPeeringConnections"]: resource_arn = cls.generate_arn( resource_id=vpc_pc["VpcPeeringConnectionId"], account_id=account_id, region=region, ) accepter_info = vpc_pc["AccepterVpcInfo"] accepter_account_id = accepter_info["OwnerId"] accepter_vpc_id = accepter_info["VpcId"] accepter_region = accepter_info["Region"] accepter_vpc_arn = VPCResourceSpec.generate_arn( resource_id=accepter_vpc_id, account_id=accepter_account_id, region=accepter_region, ) requester_info = vpc_pc["RequesterVpcInfo"] requester_account_id = requester_info["OwnerId"] requester_vpc_id = requester_info["VpcId"] requester_region = requester_info["Region"] requester_vpc_arn = VPCResourceSpec.generate_arn( resource_id=requester_vpc_id, account_id=requester_account_id, region=requester_region, ) vpc_peering_connection = { "AccepterVpc": accepter_vpc_arn, "RequesterVpc": requester_vpc_arn, "Status": vpc_pc["Status"]["Code"], } vpc_peering_connections[resource_arn] = vpc_peering_connection return ListFromAWSResult(resources=vpc_peering_connections)
class DetectorResourceSpec(GuardDutyResourceSpec): """Resource for GuardDuty Detectors""" type_name = "detector" schema = Schema( ScalarField("CreatedAt"), ScalarField("FindingPublishingFrequency"), ScalarField("ServiceRole"), ScalarField("Status"), ScalarField("UpdatedAt"), ListField( "Members", EmbeddedDictField( TransientResourceLinkField("DetectorArn", "DetectorResourceSpec", value_is_id=True), ScalarField("Email"), ScalarField("RelationshipStatus"), ScalarField("InvitedAt"), ScalarField("UpdatedAt"), ), alti_key="member", ), AnonymousDictField( "Master", ScalarField("AccountId", alti_key="master_account_id"), ScalarField("RelationshipStatus", alti_key="master_relationship_status"), # ScalarField("InvitedAt", alti_key="master_invited_at"), ScalarField("InvitedAt", alti_key="master_invited_at", optional=True), optional=True, ), ) @classmethod def list_from_aws(cls: Type["DetectorResourceSpec"], client: BaseClient, account_id: str, region: str) -> ListFromAWSResult: """Return a dict of dicts of the format: {'detector_1_arn': {detector_1_dict}, 'detector_2_arn': {detector_2_dict}, ...} Where the dicts represent results from list_detectors and list_members, get_detector for each listed detector.""" list_detectors_paginator = client.get_paginator("list_detectors") detectors: Dict[str, Dict[str, Any]] = {} for list_detectors_resp in list_detectors_paginator.paginate(): detector_ids = list_detectors_resp["DetectorIds"] for detector_id in detector_ids: resource_arn = cls.generate_arn(account_id=account_id, region=region, resource_id=detector_id) try: detectors[resource_arn] = cls.get_detector( client, detector_id, region) except ClientError as c_e: error_code = getattr(c_e, "response", {}).get("Error", {}).get("Code", {}) if error_code != "BadRequestException": raise c_e return ListFromAWSResult(resources=detectors) @classmethod def get_detector(cls: Type["DetectorResourceSpec"], client: BaseClient, detector_id: str, region: str) -> Dict[str, Any]: detector_resp = client.get_detector(DetectorId=detector_id) detector = { key: detector_resp[key] for key in ( "CreatedAt", "FindingPublishingFrequency", "ServiceRole", "Status", "UpdatedAt", ) } detector["Members"] = cls.get_detector_members(client, detector_id, region) master_account_resp = client.get_master_account(DetectorId=detector_id) master_account_dict = master_account_resp.get("Master") if master_account_dict: detector["Master"] = { # key: master_account_dict[key] key: master_account_dict.get(key) for key in ( "AccountId", "RelationshipStatus", "InvitedAt", ) } return detector @classmethod def get_detector_members(cls: Type["DetectorResourceSpec"], client: BaseClient, detector_id: str, region: str) -> List[Dict[str, Any]]: member_resps: List[Dict[str, Any]] = [] list_members_paginator = client.get_paginator("list_members") for list_members_resp in list_members_paginator.paginate( DetectorId=detector_id): member_resps += list_members_resp.get("Members", []) members = [] if member_resps: for member_resp in member_resps: member_account_id = member_resp["AccountId"] member_detector_id = member_resp["DetectorId"] # member_email = member_resp["Email"] member_email = member_resp.get("Email", "[NONE]") member_relationship_status = member_resp["RelationshipStatus"] # member_invited_at = member_resp["InvitedAt"] member_invited_at = member_resp.get("InvitedAt", "[NONE]") member_updated_at = member_resp["UpdatedAt"] member_detector_arn = cls.generate_arn( account_id=member_account_id, region=region, resource_id=member_detector_id, ) member = { "DetectorArn": member_detector_arn, "Email": member_email, "RelationshipStatus": member_relationship_status, "InvitedAt": member_invited_at, "UpdatedAt": member_updated_at, } members.append(member) return members
class ClassicLoadBalancerResourceSpec(ELBV1ResourceSpec): """Resource for classic load balancer""" type_name = "loadbalancer" schema = Schema( ScalarField("DNSName"), ScalarField("CreatedTime"), ScalarField("LoadBalancerName"), ScalarField("Scheme"), ResourceLinkField("VPCId", VPCResourceSpec, optional=True), ListField("Subnets", EmbeddedResourceLinkField(SubnetResourceSpec), optional=True), ListField("SecurityGroups", EmbeddedResourceLinkField(SecurityGroupResourceSpec), optional=True), ScalarField("Type"), ScalarField("AccessLogsEnabled"), TransientResourceLinkField( "AccessLogsS3Bucket", S3BucketResourceSpec, alti_key="access_logs_s3_bucket", optional=True, ), ScalarField("AccessLogsS3Prefix", optional=True), ) @classmethod def list_from_aws( cls: Type["ClassicLoadBalancerResourceSpec"], client: BaseClient, account_id: str, region: str, ) -> ListFromAWSResult: """Return a dict of dicts of the format: {'lb_1_arn': {lb_1_dict}, 'lb_2_arn': {lb_2_dict}, ...} Where the dicts represent results from describe_load_balancers.""" paginator = client.get_paginator("describe_load_balancers") load_balancers = {} for resp in paginator.paginate(): for lb in resp["LoadBalancerDescriptions"]: lb_name = lb["LoadBalancerName"] resource_arn = cls.generate_arn(account_id=account_id, region=region, resource_id=lb_name) try: lb_attrs = cls.get_lb_attrs(client, lb_name) lb.update(lb_attrs) lb["Type"] = "classic" load_balancers[resource_arn] = lb except ClientError as c_e: if (getattr(c_e, "response", {}).get("Error", {}).get( "Code", {}) != "LoadBalancerNotFound"): raise c_e return ListFromAWSResult(resources=load_balancers) @classmethod def get_lb_attrs( cls: Type["ClassicLoadBalancerResourceSpec"], client: BaseClient, lb_name: str, ) -> Dict[str, str]: """Get lb attributes that Altimeter graphs.""" lb_attrs = {} resp = client.describe_load_balancer_attributes( LoadBalancerName=lb_name) access_log_attrs = resp["LoadBalancerAttributes"]["AccessLog"] lb_attrs["AccessLogsEnabled"] = access_log_attrs["Enabled"] if "S3BucketName" in access_log_attrs: lb_attrs["AccessLogsS3Bucket"] = access_log_attrs["S3BucketName"] if "S3BucketPrefix" in access_log_attrs: lb_attrs["AccessLogsS3Prefix"] = access_log_attrs["S3BucketPrefix"] return lb_attrs
class LoadBalancerResourceSpec(ELBV2ResourceSpec): """Resource for load balancer""" type_name = "loadbalancer" schema = Schema( ScalarField("DNSName"), ScalarField("CreatedTime"), ScalarField("LoadBalancerName"), ScalarField("Scheme"), ResourceLinkField("VpcId", VPCResourceSpec, optional=True), AnonymousDictField("State", ScalarField("Code", alti_key="load_balancer_state")), ScalarField("Type"), ListField( "AvailabilityZones", EmbeddedDictField( ScalarField("ZoneName"), ResourceLinkField("SubnetId", SubnetResourceSpec, optional=True), ListField( "LoadBalancerAddresses", EmbeddedDictField( ScalarField("IpAddress", optional=True), ScalarField("AllocationId", optional=True), ), optional=True, ), ), ), ListField("SecurityGroups", EmbeddedResourceLinkField(SecurityGroupResourceSpec), optional=True), ScalarField("IpAddressType"), ScalarField("AccessLogsEnabled"), TransientResourceLinkField( "AccessLogsS3Bucket", S3BucketResourceSpec, alti_key="access_logs_s3_bucket", optional=True, ), ScalarField("AccessLogsS3Prefix", optional=True), ) @classmethod def list_from_aws(cls: Type["LoadBalancerResourceSpec"], client: BaseClient, account_id: str, region: str) -> ListFromAWSResult: """Return a dict of dicts of the format: {'lb_1_arn': {lb_1_dict}, 'lb_2_arn': {lb_2_dict}, ...} Where the dicts represent results from describe_load_balancers.""" paginator = client.get_paginator("describe_load_balancers") load_balancers = {} for resp in paginator.paginate(): for lb in resp.get("LoadBalancers", []): resource_arn = lb["LoadBalancerArn"] try: lb_attrs = cls.get_lb_attrs(client, resource_arn) lb.update(lb_attrs) load_balancers[resource_arn] = lb except ClientError as c_e: if (getattr(c_e, "response", {}).get("Error", {}).get( "Code", {}) != "LoadBalancerNotFound"): raise c_e return ListFromAWSResult(resources=load_balancers) @classmethod def get_lb_attrs( cls: Type["LoadBalancerResourceSpec"], client: BaseClient, lb_arn: str, ) -> Dict[str, str]: """Get lb attributes that Altimeter graphs.""" lb_attrs = {} resp = client.describe_load_balancer_attributes(LoadBalancerArn=lb_arn) for attr in resp["Attributes"]: if attr["Key"] == "access_logs.s3.enabled": lb_attrs["AccessLogsEnabled"] = attr["Value"] elif attr["Key"] == "access_logs.s3.bucket": if attr["Value"]: lb_attrs["AccessLogsS3Bucket"] = attr["Value"] elif attr["Key"] == "access_logs.s3.prefix": if attr["Value"]: lb_attrs["AccessLogsS3Prefix"] = attr["Value"] return lb_attrs