Skip to content

Commit

Permalink
feat(aws): add DirectConnect service and checks (#5522)
Browse files Browse the repository at this point in the history
  • Loading branch information
sansns authored Oct 28, 2024
1 parent 14f06d6 commit f70e3de
Show file tree
Hide file tree
Showing 12 changed files with 924 additions and 0 deletions.
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from prowler.providers.aws.services.directconnect.directconnect_service import (
DirectConnect,
)
from prowler.providers.common.provider import Provider

directconnect_client = DirectConnect(Provider.get_global_provider())
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"Provider": "aws",
"CheckID": "directconnect_connection_redundancy",
"CheckTitle": "Ensure Direct Connect connections are redundant",
"CheckType": [
"Resilience"
],
"ServiceName": "directconnect",
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:directconnect:region:account-id:directconnect/resource-id",
"Severity": "medium",
"ResourceType": "",
"Description": "Checks the resilience of the AWS Direct Connect used to connect your on-premises.",
"Risk": "This check alerts you if any Direct Connect connections are not redundant and the connections are coming from two distinct Direct Connect locations. Lack of location resiliency can result in unexpected downtime during maintenance, a fiber cut, a device failure, or a complete location failure.",
"RelatedUrl": "https://docs.aws.amazon.com/awssupport/latest/user/fault-tolerance-checks.html#amazon-direct-connect-location-resiliency",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": ""
},
"Recommendation": {
"Text": "To build Direct Connect location resiliency, you should have at least two connections from at least two distinct Direct Connect locations.",
"Url": "https://aws.amazon.com/directconnect/resiliency-recommendation/"
}
},
"Categories": [
"redundancy"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.directconnect.directconnect_client import (
directconnect_client,
)


class directconnect_connection_redundancy(Check):
def execute(self):
findings = []
if len(directconnect_client.connections):
regions = {}
for conn in directconnect_client.connections.values():
if conn.region not in regions:
regions[conn.region] = {}
regions[conn.region]["Connections"] = 0
regions[conn.region]["Locations"] = set()
regions[conn.region]["Connections"] += 1
regions[conn.region]["Locations"].add(conn.location)

for region, connections in regions.items():
report = Check_Report_AWS(self.metadata())
report.region = region
report.resource_arn = directconnect_client.audited_account_arn
report.resource_id = directconnect_client.audited_account
if connections["Connections"] == 1:
report.status = "FAIL"
report.status_extended = (
"There is only one Direct Connect connection."
)
else: # Connection Redundancy is met.
if (
len(connections["Locations"]) == 1
): # All connections use the same location
report.status = "FAIL"
report.status_extended = f"There is only one location {next(iter(connections['Locations']))} used by all the Direct Connect connections."
else: # Connection Redundancy and Location Redundancy is also met
report.status = "PASS"
report.status_extended = f"There are {connections['Connections']} Direct Connect connections across {len(connections['Locations'])} locations."

findings.append(report)

return findings
149 changes: 149 additions & 0 deletions prowler/providers/aws/services/directconnect/directconnect_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
from typing import Optional

from botocore.exceptions import ClientError
from pydantic import BaseModel

from prowler.lib.logger import logger
from prowler.lib.scan_filters.scan_filters import is_resource_filtered
from prowler.providers.aws.lib.service.service import AWSService


class DirectConnect(AWSService):
def __init__(self, provider):
super().__init__(__class__.__name__, provider)
self.connections = {}
self.vifs = {}
self.vgws = {}
self.dxgws = {}
self.__threading_call__(self._describe_connections)
self.__threading_call__(self._describe_vifs)

def _describe_connections(self, regional_client):
"""List DirectConnect(s) in the given region.
Args:
regional_client: The regional AWS client.
"""

try:
logger.info("DirectConnect - Listing Connections...")
dx_connect = regional_client.describe_connections()

for connection in dx_connect["connections"]:
connection_arn = f"arn:{self.audited_partition}:directconnect:{regional_client.region}:{self.audited_account}:dxcon/{connection['connectionId']}"
if not self.audit_resources or (
is_resource_filtered(connection_arn, self.audit_resources)
):
self.connections[connection_arn] = Connection(
arn=connection_arn,
id=connection["connectionId"],
name=connection["connectionName"],
location=connection["location"],
region=regional_client.region,
)

except Exception as error:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)

def _describe_vifs(self, regional_client):
"""Describe each DirectConnect VIFs."""

logger.info("DirectConnect - Describing VIFs...")
try:
describe_vifs = regional_client.describe_virtual_interfaces()
for vif in describe_vifs["virtualInterfaces"]:
vif_id = vif["virtualInterfaceId"]
vif_arn = f"arn:{self.audited_partition}:directconnect:{regional_client.region}:{self.audited_account}:dxvif/{vif_id}"
if not self.audit_resources or (
is_resource_filtered(vif_arn, self.audit_resources)
):
vgw_id = vif.get("virtualGatewayId")
connection_id = vif.get("connectionId")
dxgw_id = vif.get("directConnectGatewayId")
self.vifs[vif_arn] = VirtualInterface(
arn=vif_arn,
id=vif_id,
name=vif["virtualInterfaceName"],
connection_id=connection_id,
vgw_gateway_id=vif["virtualGatewayId"],
dx_gateway_id=dxgw_id,
location=vif["location"],
region=regional_client.region,
)
if vgw_id:
vgw_arn = f"arn:{self.audited_partition}:directconnect:{regional_client.region}:{self.audited_account}:virtual-gateway/{vgw_id}"
if vgw_arn in self.vgws:
self.vgws[vgw_arn].vifs.append(vif_id)
self.vgws[vgw_arn].connections.append(connection_id)
else:
self.vgws[vgw_arn] = VirtualGateway(
arn=vgw_arn,
id=vgw_id,
vifs=[vif_id],
connections=[connection_id],
region=regional_client.region,
)

if dxgw_id:
dxgw_arn = f"arn:{self.audited_partition}:directconnect:{regional_client.region}:{self.audited_account}:dx-gateway/{dxgw_id}"
if dxgw_arn in self.dxgws:
self.dxgws[dxgw_arn].vifs.append(vif_id)
self.dxgws[dxgw_arn].connections.append(connection_id)
else:
self.dxgws[dxgw_arn] = DXGateway(
arn=dxgw_arn,
id=dxgw_id,
vifs=[vif_id],
connections=[connection_id],
region=regional_client.region,
)
except ClientError as error:
if error.response["Error"]["Code"] == "ResourceNotFoundException":
logger.warning(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
else:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
except Exception as error:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)


class Connection(BaseModel):
arn = str
id: str
name: Optional[str] = None
location: str
region: str


class VirtualInterface(BaseModel):
arn: str
id: str
name: str
connection_id: Optional[str] = None
vgw_gateway_id: str
dx_gateway_id: str
location: str
region: str


class VirtualGateway(BaseModel):
arn: str
id: str
vifs: list
connections: list
region: str


class DXGateway(BaseModel):
arn: str
id: str
vifs: list
connections: list
region: str
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"Provider": "aws",
"CheckID": "directconnect_virtual_interface_redundancy",
"CheckTitle": "Ensure Direct Connect virtual interface(s) are providing redundant connections",
"CheckType": [
"Resilience"
],
"ServiceName": "directconnect",
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:directconnect:region:account-id:directconnect/resource-id",
"Severity": "medium",
"ResourceType": "",
"Description": "Checks the resilience of the AWS Direct Connect used to connect your on-premises to each Direct Connect gateway or virtual private gateway.",
"Risk": "This check alerts you if any Direct Connect gateway or virtual private gateway isn't configured with virtual interfaces across at least two distinct Direct Connect locations. Lack of location resiliency can result in unexpected downtime during maintenance, a fiber cut, a device failure, or a complete location failure.",
"RelatedUrl": "https://docs.aws.amazon.com/awssupport/latest/user/fault-tolerance-checks.html#amazon-direct-connect-location-resiliency",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": ""
},
"Recommendation": {
"Text": "To build Direct Connect location resiliency, you can configure the Direct Connect gateway or virtual private gateway to connect to at least two distinct Direct Connect locations.",
"Url": "https://aws.amazon.com/directconnect/resiliency-recommendation/"
}
},
"Categories": [
"redundancy"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.directconnect.directconnect_client import (
directconnect_client,
)


class directconnect_virtual_interface_redundancy(Check):
def execute(self):
findings = []
for vgw in directconnect_client.vgws.values():
report = Check_Report_AWS(self.metadata())
report.resource_arn = vgw.arn
report.region = vgw.region
report.resource_id = vgw.id
if len(vgw.vifs) < 2:
report.status = "FAIL"
report.status_extended = (
f"Virtual private gateway {vgw.id} only has one VIF."
)
elif len(vgw.connections) < 2:
report.status = "FAIL"
report.status_extended = f"Virtual private gateway {vgw.id} has more than 1 VIFs, but all the VIFs are on the same DX Connection."
else:
report.status = "PASS"
report.status_extended = f"Virtual private gateway {vgw.id} has more than 1 VIFs and the VIFs are on more than one DX connection."

findings.append(report)

for dxgw in directconnect_client.dxgws.values():
report = Check_Report_AWS(self.metadata())
report.region = dxgw.region
report.resource_arn = dxgw.arn
report.resource_id = dxgw.id
if len(dxgw.vifs) < 2:
report.status = "FAIL"
report.status_extended = (
f"Direct Connect gateway {dxgw.id} only has one VIF."
)
elif len(dxgw.connections) < 2:
report.status = "FAIL"
report.status_extended = f"Direct Connect gateway {dxgw.id} has more than 1 VIFs, but all the VIFs are on the same DX Connection."
else:
report.status = "PASS"
report.status_extended = f"Direct Connect gateway {dxgw.id} has more than 1 VIFs and the VIFs are on more than one DX connection."

findings.append(report)

return findings
Loading

0 comments on commit f70e3de

Please sign in to comment.