feat(e2e): provider for e2e cloud (#11654)

Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
This commit is contained in:
UniCode
2026-07-08 17:51:27 +05:30
committed by GitHub
parent 991c204a88
commit d874fc573d
180 changed files with 6428 additions and 4 deletions
+24
View File
@@ -615,6 +615,30 @@ jobs:
flags: prowler-py${{ matrix.python-version }}-linode
files: ./linode_coverage.xml
# E2E Networks Provider
- name: Check if E2E Networks files changed
if: steps.check-changes.outputs.any_changed == 'true'
id: changed-e2enetworks
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
with:
files: |
./prowler/**/e2enetworks/**
./tests/**/e2enetworks/**
./uv.lock
- name: Run E2E Networks tests
if: steps.changed-e2enetworks.outputs.any_changed == 'true'
run: uv run pytest -n auto --cov=./prowler/providers/e2enetworks --cov-report=xml:e2enetworks_coverage.xml tests/providers/e2enetworks
- name: Upload E2E Networks coverage to Codecov
if: steps.changed-e2enetworks.outputs.any_changed == 'true'
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
flags: prowler-py${{ matrix.python-version }}-e2enetworks
files: ./e2enetworks_coverage.xml
# External Provider (dynamic loading)
- name: Check if External Provider files changed
if: steps.check-changes.outputs.any_changed == 'true'
+1
View File
@@ -141,6 +141,7 @@ Every AWS provider scan will enqueue an Attack Paths ingestion job automatically
| Vercel | 26 | 6 | 1 | 8 | Official | UI, API, CLI |
| Okta | 29 | 8 | 2 | 2 | Official | UI, API, CLI |
| Linode [Contact us](https://prowler.com/contact) | 10 | 3 | 1 | 4 | Unofficial | CLI |
| E2E Networks [Contact us](https://prowler.com/contact) | 27 | 6 | 0 | 2 | Unofficial | CLI |
| Scaleway [Contact us](https://prowler.com/contact) | 1 | 1 | 1 | 1 | Unofficial | CLI |
| StackIT [Contact us](https://prowler.com/contact) | 7 | 2 | 1 | 3 | Unofficial | CLI |
| NHN | 6 | 2 | 2 | 0 | Unofficial | CLI |
+7
View File
@@ -378,6 +378,13 @@
"user-guide/providers/linode/getting-started-linode",
"user-guide/providers/linode/authentication"
]
},
{
"group": "E2E Networks",
"pages": [
"user-guide/providers/e2enetworks/getting-started-e2enetworks",
"user-guide/providers/e2enetworks/authentication"
]
}
]
},
+1
View File
@@ -31,6 +31,7 @@ Prowler supports a wide range of providers organized by category:
| [AWS](/user-guide/providers/aws/getting-started-aws) | Official | Accounts | UI, API, CLI |
| [Azure](/user-guide/providers/azure/getting-started-azure) | Official | Subscriptions | UI, API, CLI |
| [Cloudflare](/user-guide/providers/cloudflare/getting-started-cloudflare) | Official | Accounts | UI, API, CLI |
| [E2E Networks](/user-guide/providers/e2enetworks/getting-started-e2enetworks) | [Contact us](https://prowler.com/contact) | Projects | CLI |
| [Google Cloud](/user-guide/providers/gcp/getting-started-gcp) | Official | Projects | UI, API, CLI |
| [Linode](/user-guide/providers/linode/getting-started-linode) | [Contact us](https://prowler.com/contact) | Accounts | CLI |
| **NHN** | [Contact us](https://prowler.com/contact) | Tenants | CLI |
@@ -0,0 +1,99 @@
---
title: "E2E Networks Authentication in Prowler"
---
import { VersionBadge } from "/snippets/version-badge.mdx"
<VersionBadge version="5.34.0" />
Prowler for E2E Networks authenticates against the E2E Networks MyAccount API using an **API key**, an **auth token**, and a numeric **project ID**. Set the API key and auth token as environment variables to avoid exposing secrets in shell history or process listings. Compatibility CLI flags are still accepted, but Prowler warns when secret values are passed directly on the command line.
## Required Credentials
Prowler requires read access to the E2E Networks project. The following values are needed:
| Credential | Environment Variable | Description |
|------------|----------------------|-------------|
| API Key | `E2E_NETWORKS_API_KEY` | Identifies the MyAccount API key used for requests |
| Auth Token | `E2E_NETWORKS_AUTH_TOKEN` | Bearer token authorizing the API key |
| Project ID | `E2E_NETWORKS_PROJECT_ID` | Numeric ID of the project to scan |
| Region | `E2E_NETWORKS_REGION` | Optional region to scan (defaults to all regions: Delhi, Chennai) |
<Warning>
The project ID must be an integer. A missing or non-numeric project ID stops the scan before any resource is read.
</Warning>
---
## API Credentials
### Step 1: Create the API Key and Auth Token
1. Log into the [E2E Networks MyAccount portal](https://myaccount.e2enetworks.com).
2. Open the **API Tokens** section under the account settings.
3. Create a new API token and copy both the API key and the auth token.
4. Copy the values immediately — the auth token is not shown again.
### Step 2: Find the Project ID
Select the target project in MyAccount and copy its numeric project ID from the project settings or the API context.
### Step 3: Configure Authentication
Export the credentials as environment variables:
```bash
export E2E_NETWORKS_API_KEY="your-api-key"
export E2E_NETWORKS_AUTH_TOKEN="your-auth-token"
export E2E_NETWORKS_PROJECT_ID="your-project-id"
```
Then run Prowler:
```bash
prowler e2enetworks
```
---
## Verifying Authentication
To confirm that Prowler can reach the E2E Networks project, run a scan against a single location:
```bash
prowler e2enetworks --region Delhi
```
A successful run reports findings for the discovered resources. A failed run displays an error message indicating the credential or connectivity issue.
---
## CI/CD Integration
For automated pipelines, set the credentials as secret environment variables:
**GitHub Actions:**
```yaml
env:
E2E_NETWORKS_API_KEY: ${{ secrets.E2E_NETWORKS_API_KEY }}
E2E_NETWORKS_AUTH_TOKEN: ${{ secrets.E2E_NETWORKS_AUTH_TOKEN }}
E2E_NETWORKS_PROJECT_ID: ${{ secrets.E2E_NETWORKS_PROJECT_ID }}
steps:
- name: Run Prowler
run: prowler e2enetworks
```
**GitLab CI:**
```yaml
variables:
E2E_NETWORKS_API_KEY: $E2E_NETWORKS_API_KEY
E2E_NETWORKS_AUTH_TOKEN: $E2E_NETWORKS_AUTH_TOKEN
E2E_NETWORKS_PROJECT_ID: $E2E_NETWORKS_PROJECT_ID
prowler_scan:
script:
- prowler e2enetworks
```
@@ -0,0 +1,66 @@
---
title: 'Getting Started With E2E Networks on Prowler'
---
import { VersionBadge } from "/snippets/version-badge.mdx"
<VersionBadge version="5.34.0" />
Prowler for E2E Networks scans E2E Networks infrastructure for security misconfigurations across compute nodes, networking, security groups, load balancers, storage, and managed databases.
<Note>
E2E Networks support in Prowler is community-maintained. For commercial support or to request additional service coverage, [contact us](https://prowler.com/contact).
</Note>
## Prerequisites
Set up authentication for E2E Networks with the [E2E Networks Authentication](/user-guide/providers/e2enetworks/authentication) guide before starting:
- Create an E2E Networks API key and auth token from the MyAccount API Tokens section
- Note the numeric project ID of the project to scan
## Prowler CLI
### Run Prowler for E2E Networks
Once authenticated, export the credentials as environment variables and run Prowler for E2E Networks. Environment variables keep secrets out of shell history and process listings:
```bash
export E2E_NETWORKS_API_KEY="your-api-key"
export E2E_NETWORKS_AUTH_TOKEN="your-auth-token"
export E2E_NETWORKS_PROJECT_ID="your-project-id"
prowler e2enetworks
```
### Run Specific Checks
```bash
prowler e2enetworks --checks node_encryption_enabled storage_block_volume_not_orphaned
```
### Run a Specific Service
```bash
prowler e2enetworks --services node
```
### Scan Specific Regions
Use `--region` (aliases `--filter-region` and `-f`) to limit the scan to one or more E2E Networks regions. When the flag is omitted, Prowler reads `E2E_NETWORKS_REGION` and otherwise scans all regions (Delhi, Chennai).
```bash
prowler e2enetworks --region Delhi Chennai
```
## Available Services
Prowler for E2E Networks currently supports the following services:
| Service | Description |
|---------|-------------|
| `database` | Managed database clusters and their backup, network, and encryption settings |
| `loadbalancer` | Application load balancers and their TLS, health-check, and protection settings |
| `network` | Virtual Private Clouds, peering, and reserved public IP addresses |
| `node` | Compute nodes and their encryption, protection, and network configuration |
| `securitygroup` | Security groups and their inbound and default traffic rules |
| `storage` | Block volumes and file storage (EFS/EPFS) with their protection settings |
+5
View File
@@ -140,6 +140,7 @@ from prowler.providers.azure.models import AzureOutputOptions
from prowler.providers.cloudflare.models import CloudflareOutputOptions
from prowler.providers.common.provider import Provider
from prowler.providers.common.quick_inventory import run_provider_quick_inventory
from prowler.providers.e2enetworks.models import E2eNetworksOutputOptions
from prowler.providers.gcp.models import GCPOutputOptions
from prowler.providers.github.models import GithubOutputOptions
from prowler.providers.googleworkspace.models import GoogleWorkspaceOutputOptions
@@ -432,6 +433,10 @@ def prowler():
output_options = VercelOutputOptions(
args, bulk_checks_metadata, global_provider.identity
)
elif provider == "e2enetworks":
output_options = E2eNetworksOutputOptions(
args, bulk_checks_metadata, global_provider.identity
)
elif provider == "okta":
output_options = OktaOutputOptions(
args, bulk_checks_metadata, global_provider.identity
@@ -0,0 +1 @@
E2E Networks provider with 27 checks across compute nodes, networking, security groups, load balancers, block/file storage, and managed databases
+1
View File
@@ -81,6 +81,7 @@ class Provider(str, Enum):
OKTA = "okta"
STACKIT = "stackit"
LINODE = "linode"
E2ENETWORKS = "e2enetworks"
# Compliance
+5
View File
@@ -801,3 +801,8 @@ openstack:
# Regex patterns whose matches are excluded from secret scanning of
# resource metadata.
secrets_ignore_patterns: []
# E2E Networks Configuration
e2enetworks:
# load_balancer_bitninja_enabled
require_bitninja_on_load_balancers: false
@@ -0,0 +1,23 @@
### Account, Check and/or Region can be * to apply for all the cases.
### Account == * (E2E Networks findings are matched by resource, so use * to apply to all)
### Region == <E2E Networks location, e.g. Delhi> or * for all locations
### Resources and tags are lists that can have either Regex or Keywords.
### Tags is an optional list that matches on tuples of 'key=value' and are "ANDed" together.
### Use an alternation Regex to match one of multiple tags with "ORed" logic.
### For each check you can except Accounts, Regions, Resources and/or Tags.
########################### MUTELIST EXAMPLE ###########################
Mutelist:
Accounts:
"*":
Checks:
"node_public_ip_not_assigned":
Regions:
- "Delhi"
Resources:
- "example-node-id"
- "bastion-.*"
"storage_bucket_public_access_disabled":
Regions:
- "*"
Resources:
- "public-assets-bucket"
+22
View File
@@ -0,0 +1,22 @@
"""E2E Networks provider config schema."""
from typing import Optional
from pydantic import Field
from prowler.config.schema.base import ProviderConfigBase
class E2eNetworksProviderConfig(ProviderConfigBase):
"""E2E Networks provider configuration schema.
Defines optional configuration parameters for E2E Networks security checks.
"""
require_bitninja_on_load_balancers: Optional[bool] = Field(
default=None,
description=(
"Whether BitNinja protection is required on load balancers for the "
"loadbalancer_bitninja_enabled check."
),
)
+2
View File
@@ -9,6 +9,7 @@ from prowler.config.schema.aws import AWSProviderConfig
from prowler.config.schema.azure import AzureProviderConfig
from prowler.config.schema.base import ProviderConfigBase
from prowler.config.schema.cloudflare import CloudflareProviderConfig
from prowler.config.schema.e2enetworks import E2eNetworksProviderConfig
from prowler.config.schema.gcp import GCPProviderConfig
from prowler.config.schema.github import GitHubProviderConfig
from prowler.config.schema.kubernetes import KubernetesProviderConfig
@@ -27,6 +28,7 @@ SCHEMAS: dict[str, type[ProviderConfigBase]] = {
"github": GitHubProviderConfig,
"mongodbatlas": MongoDBAtlasProviderConfig,
"cloudflare": CloudflareProviderConfig,
"e2enetworks": E2eNetworksProviderConfig,
"vercel": VercelProviderConfig,
"okta": OktaProviderConfig,
"alibabacloud": AlibabaCloudProviderConfig,
+23
View File
@@ -1286,6 +1286,29 @@ class CheckReportNHN(Check_Report):
self.location = getattr(resource, "location", "kr1")
@dataclass
class CheckReportE2eNetworks(Check_Report):
"""Contains the E2E Networks Check's finding information."""
resource_name: str
resource_id: str
location: str
def __init__(self, metadata: Dict, resource: Any) -> None:
"""Initialize the E2E Networks Check's finding information.
Args:
metadata: The metadata of the check.
resource: Basic information about the E2E Networks resource.
"""
super().__init__(metadata, resource)
self.resource_name = getattr(
resource, "name", getattr(resource, "resource_name", "")
)
self.resource_id = getattr(resource, "id", getattr(resource, "resource_id", ""))
self.location = getattr(resource, "location", "global")
@dataclass
class CheckReportStackIT(Check_Report):
"""Contains the StackIT Check's finding information."""
+5 -3
View File
@@ -48,6 +48,7 @@ class ProwlerArgumentParser:
"nhn",
"mongodbatlas",
"vercel",
"e2enetworks",
"okta",
"scaleway",
"stackit",
@@ -74,10 +75,10 @@ class ProwlerArgumentParser:
self.parser = argparse.ArgumentParser(
prog="prowler",
formatter_class=RawTextHelpFormatter,
usage=f"prowler [-h] [--version] {{aws,azure,gcp,kubernetes,m365,github,googleworkspace,okta,nhn,mongodbatlas,oraclecloud,alibabacloud,cloudflare,openstack,scaleway,stackit,vercel,linode,dashboard,iac,image,llm{extra_providers_csv}}} ...",
usage=f"prowler [-h] [--version] {{aws,azure,gcp,kubernetes,m365,github,googleworkspace,okta,nhn,mongodbatlas,oraclecloud,alibabacloud,cloudflare,openstack,scaleway,stackit,vercel,linode,e2enetworks,dashboard,iac,image,llm{extra_providers_csv}}} ...",
epilog=f"""
Available Cloud Providers:
{{aws,azure,gcp,kubernetes,m365,github,googleworkspace,okta,iac,llm,image,nhn,mongodbatlas,oraclecloud,alibabacloud,cloudflare,openstack,scaleway,stackit,vercel,linode{extra_providers_csv}}}
{{aws,azure,gcp,kubernetes,m365,github,googleworkspace,okta,iac,llm,image,nhn,mongodbatlas,oraclecloud,alibabacloud,cloudflare,openstack,scaleway,stackit,vercel,linode,e2enetworks{extra_providers_csv}}}
aws AWS Provider
azure Azure Provider
gcp GCP Provider
@@ -98,7 +99,8 @@ Available Cloud Providers:
mongodbatlas MongoDB Atlas Provider
scaleway Scaleway Provider
vercel Vercel Provider
linode Linode Provider{extra_providers_text}
linode Linode Provider
e2enetworks E2E Networks Provider{extra_providers_text}
Available components:
@@ -26,6 +26,7 @@ PROVIDER_HEADER_MAP = {
"oraclecloud": ("TenancyId", "account_uid", "Region", "region"),
"alibabacloud": ("AccountId", "account_uid", "Region", "region"),
"nhn": ("AccountId", "account_uid", "Region", "region"),
"e2enetworks": ("ProjectId", "account_uid", "Location", "region"),
}
_DEFAULT_HEADERS = ("AccountId", "account_uid", "Region", "region")
+12
View File
@@ -342,6 +342,18 @@ class Finding(BaseModel):
output_data["resource_uid"] = check_output.resource_id
output_data["region"] = check_output.location
elif provider.type == "e2enetworks":
output_data["auth_method"] = "api_key_and_bearer_token"
output_data["account_uid"] = str(
get_nested_attribute(provider, "identity.project_id")
)
output_data["account_name"] = str(
get_nested_attribute(provider, "identity.project_id")
)
output_data["resource_name"] = check_output.resource_name
output_data["resource_uid"] = check_output.resource_id
output_data["region"] = check_output.location
elif provider.type == "stackit":
output_data["auth_method"] = getattr(
provider, "auth_method", "api_token"
+41
View File
@@ -2,6 +2,7 @@ import sys
from io import TextIOWrapper
import markdown
from markupsafe import escape
from prowler.config.config import (
html_logo_url,
@@ -1396,6 +1397,46 @@ class HTML(Output):
)
return ""
@staticmethod
def get_e2enetworks_assessment_summary(provider: Provider) -> str:
"""Get the HTML assessment summary for the E2E Networks provider."""
try:
locations = escape(", ".join(provider.identity.locations))
project_id = escape(str(provider.identity.project_id))
return f"""
<div class="col-md-2">
<div class="card">
<div class="card-header">
E2E Networks Assessment Summary
</div>
<ul class="list-group list-group-flush">
<li class="list-group-item">
<b>Project ID:</b> {project_id}
</li>
<li class="list-group-item">
<b>Locations:</b> {locations}
</li>
</ul>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-header">
E2E Networks Credentials
</div>
<ul class="list-group list-group-flush">
<li class="list-group-item">
<b>Authentication:</b> API Key + Bearer Token
</li>
</ul>
</div>
</div>"""
except Exception as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}] -- {error}"
)
return ""
@staticmethod
def get_vercel_assessment_summary(provider: Provider) -> str:
"""
+2
View File
@@ -24,6 +24,8 @@ def stdout_report(finding, color, verbose, status, fix, provider=None):
details = finding.location
elif finding.check_metadata.Provider == "nhn":
details = finding.location
elif finding.check_metadata.Provider == "e2enetworks":
details = finding.location
elif finding.check_metadata.Provider == "stackit":
details = finding.location
elif finding.check_metadata.Provider == "llm":
+3
View File
@@ -70,6 +70,9 @@ def display_summary_table(
elif provider.type == "nhn":
entity_type = "Tenant Domain"
audited_entities = provider.identity.tenant_domain
elif provider.type == "e2enetworks":
entity_type = "Project"
audited_entities = str(provider.identity.project_id)
elif provider.type == "stackit":
if provider.identity.project_name:
entity_type = "Project"
+10
View File
@@ -625,6 +625,16 @@ class Provider(ABC):
mutelist_path=arguments.mutelist_file,
fixer_config=fixer_config,
)
elif arguments.provider == "e2enetworks":
provider_class(
api_key=getattr(arguments, "e2e_networks_api_key", None),
auth_token=getattr(arguments, "e2e_networks_auth_token", None),
project_id=getattr(arguments, "e2e_networks_project_id", None),
locations=getattr(arguments, "region", None),
config_path=arguments.config_file,
mutelist_path=arguments.mutelist_file,
fixer_config=fixer_config,
)
elif arguments.provider == "okta":
provider_class(
okta_org_domain=getattr(arguments, "okta_org_domain", ""),
@@ -0,0 +1,258 @@
import os
import requests
from colorama import Fore, Style
from prowler.config.config import (
default_config_file_path,
get_default_mute_file_path,
load_and_validate_config_file,
)
from prowler.lib.logger import logger
from prowler.lib.utils.utils import print_boxes
from prowler.providers.common.models import Audit_Metadata, Connection
from prowler.providers.common.provider import Provider
from prowler.providers.e2enetworks.exceptions.exceptions import (
E2eNetworksCredentialsError,
E2eNetworksSessionError,
)
from prowler.providers.e2enetworks.lib.mutelist.mutelist import E2eNetworksMutelist
from prowler.providers.e2enetworks.models import (
E2E_DEFAULT_LOCATIONS,
E2eNetworksIdentityInfo,
E2eNetworksSession,
)
class E2enetworksProvider(Provider):
"""Provider class for E2E Networks."""
_type: str = "e2enetworks"
_session: E2eNetworksSession
_identity: E2eNetworksIdentityInfo
_audit_config: dict
_fixer_config: dict
_mutelist: E2eNetworksMutelist
audit_metadata: Audit_Metadata
def __init__(
self,
api_key: str = None,
auth_token: str = None,
project_id: str | int = None,
locations: list[str] | None = None,
config_path: str = None,
fixer_config: dict = None,
mutelist_path: str = None,
mutelist_content: dict = None,
):
logger.info("Initializing E2E Networks Provider...")
self._api_key = api_key or os.getenv("E2E_NETWORKS_API_KEY")
self._auth_token = auth_token or os.getenv("E2E_NETWORKS_AUTH_TOKEN")
project_value = project_id or os.getenv("E2E_NETWORKS_PROJECT_ID")
self._project_id = int(project_value) if project_value else None
self._locations = self._resolve_locations(locations)
if not self._api_key or not self._auth_token or self._project_id is None:
raise E2eNetworksCredentialsError(
message="E2enetworksProvider requires api_key, auth_token, and project_id."
)
self._fixer_config = fixer_config if fixer_config else {}
if not config_path:
config_path = default_config_file_path
self._audit_config = load_and_validate_config_file(self._type, config_path)
if mutelist_content is not None:
self._mutelist = E2eNetworksMutelist(mutelist_content=mutelist_content)
else:
if not mutelist_path:
mutelist_path = get_default_mute_file_path(self._type)
self._mutelist = E2eNetworksMutelist(mutelist_path=mutelist_path)
self._session = E2enetworksProvider.setup_session(
api_key=self._api_key,
auth_token=self._auth_token,
project_id=self._project_id,
locations=self._locations,
)
self._identity = E2eNetworksIdentityInfo(
project_id=self._project_id,
locations=self._locations,
)
Provider.set_global_provider(self)
@staticmethod
def _resolve_locations(locations: list[str] | None) -> list[str]:
"""Resolve scan locations from CLI args, env vars, or defaults.
Args:
locations: Optional list of location names from CLI arguments.
Returns:
The resolved list of E2E Networks locations to scan.
"""
if locations:
return locations
env_region = os.getenv("E2E_NETWORKS_REGION")
if env_region:
return [env_region]
return list(E2E_DEFAULT_LOCATIONS)
@property
def type(self) -> str:
return self._type
@property
def session(self) -> E2eNetworksSession:
return self._session
@property
def identity(self) -> E2eNetworksIdentityInfo:
return self._identity
@property
def audit_config(self) -> dict:
return self._audit_config
@property
def fixer_config(self) -> dict:
return self._fixer_config
@property
def mutelist(self) -> E2eNetworksMutelist:
return self._mutelist
@staticmethod
def setup_session(
api_key: str,
auth_token: str,
project_id: int,
locations: list[str],
) -> E2eNetworksSession:
"""Create an authenticated E2E Networks API session.
Args:
api_key: E2E Networks API key.
auth_token: Bearer auth token for the MyAccount API.
project_id: E2E Networks project identifier.
locations: Locations included in the session scope.
Returns:
A configured E2eNetworksSession with an HTTP client.
Raises:
E2eNetworksSessionError: If session initialization fails.
"""
try:
http_session = requests.Session()
http_session.headers.update(
{
"Authorization": f"Bearer {auth_token}",
"Content-Type": "application/json",
}
)
return E2eNetworksSession(
api_key=api_key,
auth_token=auth_token,
project_id=project_id,
locations=locations,
http_session=http_session,
)
except Exception as error:
raise E2eNetworksSessionError(
message="Failed to initialize E2E Networks session.",
original_exception=error,
) from error
def print_credentials(self) -> None:
"""Print the E2E Networks scan scope to stdout.
The API key and auth token are never printed, matching the behavior of
the other Prowler providers, which only report identity and scope.
"""
report_lines = [
f" Authentication: {Fore.YELLOW}API Key{Style.RESET_ALL}",
f" Project ID: {self._project_id}",
f" Locations: {', '.join(self._locations)}",
]
report_title = (
f"{Style.BRIGHT}Using the E2E Networks credentials below:{Style.RESET_ALL}"
)
print_boxes(report_lines, report_title)
@staticmethod
def test_connection(
api_key: str = None,
auth_token: str = None,
project_id: str | int = None,
locations: list[str] | None = None,
raise_on_exception: bool = True,
) -> Connection:
"""Test connectivity to the E2E Networks MyAccount API.
Args:
api_key: E2E Networks API key. Falls back to E2E_NETWORKS_API_KEY.
auth_token: Bearer auth token. Falls back to E2E_NETWORKS_AUTH_TOKEN.
project_id: Project identifier. Falls back to E2E_NETWORKS_PROJECT_ID.
locations: Optional locations to use for the probe request.
raise_on_exception: Whether to re-raise caught exceptions.
Returns:
Connection indicating success or containing the error.
Raises:
E2eNetworksCredentialsError: If required credentials are missing.
E2eNetworksSessionError: If the API returns a non-200 response.
Exception: Any unexpected error when raise_on_exception is True.
"""
try:
api_key = api_key or os.getenv("E2E_NETWORKS_API_KEY")
auth_token = auth_token or os.getenv("E2E_NETWORKS_AUTH_TOKEN")
project_value = project_id or os.getenv("E2E_NETWORKS_PROJECT_ID")
project_id_int = int(project_value) if project_value else None
resolved_locations = locations or (
[os.getenv("E2E_NETWORKS_REGION")]
if os.getenv("E2E_NETWORKS_REGION")
else list(E2E_DEFAULT_LOCATIONS)
)
if not api_key or not auth_token or project_id_int is None:
raise E2eNetworksCredentialsError(
message="E2E Networks test_connection requires api_key, auth_token, and project_id."
)
session = E2enetworksProvider.setup_session(
api_key=api_key,
auth_token=auth_token,
project_id=project_id_int,
locations=resolved_locations,
)
response = session.http_session.get(
f"{session.base_url}/nodes/",
params={
"apikey": session.api_key,
"project_id": session.project_id,
"location": resolved_locations[0],
},
timeout=30,
)
if response.status_code != 200:
error_msg = (
f"E2E Networks connection failed with status {response.status_code}: "
f"{response.text}"
)
raise E2eNetworksSessionError(message=error_msg)
return Connection(is_connected=True)
except Exception as error:
logger.critical(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
if raise_on_exception:
raise error
return Connection(error=error)
@@ -0,0 +1,93 @@
from prowler.exceptions.exceptions import ProwlerException
# Exceptions codes from 19000 to 19999 are reserved for E2E Networks exceptions
class E2eNetworksBaseException(ProwlerException):
"""Base class for E2E Networks errors."""
E2E_NETWORKS_ERROR_CODES = {
(19000, "E2eNetworksCredentialsError"): {
"message": "E2E Networks credentials not found or invalid",
"remediation": "Provide a valid API key, auth token and project id via the E2E_NETWORKS_API_KEY, E2E_NETWORKS_AUTH_TOKEN and E2E_NETWORKS_PROJECT_ID environment variables.",
},
(19001, "E2eNetworksAuthenticationError"): {
"message": "E2E Networks authentication failed",
"remediation": "Verify the E2E Networks API key and auth token and ensure the project id is correct and accessible.",
},
(19002, "E2eNetworksSessionError"): {
"message": "E2E Networks session setup failed",
"remediation": "Review the E2E Networks credentials and network connectivity to the MyAccount API.",
},
(19003, "E2eNetworksAPIError"): {
"message": "E2E Networks API request failed",
"remediation": "Check the E2E Networks MyAccount API status and that the credentials grant access to the requested resource.",
},
(19004, "E2eNetworksIdentityError"): {
"message": "Unable to retrieve E2E Networks identity or project information",
"remediation": "Ensure the credentials allow access to the E2E Networks project and account APIs.",
},
}
def __init__(self, code, file=None, original_exception=None, message=None):
provider = "E2E"
error_info = self.E2E_NETWORKS_ERROR_CODES.get((code, self.__class__.__name__))
if error_info is None:
error_info = {
"message": message or "Unknown E2E Networks error",
"remediation": "Check the E2E Networks MyAccount API documentation for more details.",
}
elif message:
error_info = error_info.copy()
error_info["message"] = message
super().__init__(
code=code,
source=provider,
file=file,
original_exception=original_exception,
error_info=error_info,
)
class E2eNetworksCredentialsError(E2eNetworksBaseException):
"""Exception for E2E Networks credential errors."""
def __init__(self, file=None, original_exception=None, message=None):
super().__init__(
19000, file=file, original_exception=original_exception, message=message
)
class E2eNetworksAuthenticationError(E2eNetworksBaseException):
"""Exception for E2E Networks authentication errors."""
def __init__(self, file=None, original_exception=None, message=None):
super().__init__(
19001, file=file, original_exception=original_exception, message=message
)
class E2eNetworksSessionError(E2eNetworksBaseException):
"""Exception for E2E Networks session setup errors."""
def __init__(self, file=None, original_exception=None, message=None):
super().__init__(
19002, file=file, original_exception=original_exception, message=message
)
class E2eNetworksAPIError(E2eNetworksBaseException):
"""Exception for E2E Networks API request errors."""
def __init__(self, file=None, original_exception=None, message=None):
super().__init__(
19003, file=file, original_exception=original_exception, message=message
)
class E2eNetworksIdentityError(E2eNetworksBaseException):
"""Exception for E2E Networks identity errors."""
def __init__(self, file=None, original_exception=None, message=None):
super().__init__(
19004, file=file, original_exception=original_exception, message=message
)
@@ -0,0 +1,109 @@
from __future__ import annotations
import re
from typing import Any
import requests
from prowler.lib.logger import logger
from prowler.providers.e2enetworks.exceptions.exceptions import E2eNetworksAPIError
from prowler.providers.e2enetworks.models import E2eNetworksSession
def _redact_sensitive_values(message: str) -> str:
"""Redact E2E Networks secrets from request error messages."""
redacted = re.sub(r"(?i)(apikey=)[^&\s]+", r"\1REDACTED", message)
return re.sub(
r"(?i)(Authorization:\s*Bearer\s+|Bearer\s+)[^\s,;]+",
r"\1REDACTED",
redacted,
)
class E2eNetworksAPIClient:
"""Shared HTTP client for E2E Networks MyAccount API requests."""
def __init__(self, session: E2eNetworksSession):
self.session = session
def _auth_params(self, location: str, extra: dict | None = None) -> dict[str, Any]:
params = {
"apikey": self.session.api_key,
"project_id": self.session.project_id,
"location": location,
}
if extra:
params.update(extra)
return params
def get(
self,
path: str,
location: str,
params: dict | None = None,
timeout: int = 30,
) -> dict:
"""Perform a GET request against the E2E Networks API."""
url = f"{self.session.base_url}{path}"
query_params = self._auth_params(location, params)
try:
response = self.session.http_session.get(
url,
params=query_params,
timeout=timeout,
)
response.raise_for_status()
payload = response.json()
if not isinstance(payload, dict):
return {"data": payload}
return payload
except requests.exceptions.RequestException as error:
redacted_error = _redact_sensitive_values(str(error))
logger.error(
f"E2E API GET {path} failed: {error.__class__.__name__}: {redacted_error}"
)
raise E2eNetworksAPIError(
message=f"GET {path} failed for location {location}",
original_exception=Exception(redacted_error),
) from error
def get_data(
self,
path: str,
location: str,
params: dict | None = None,
) -> list | dict:
"""Return the `data` field from a standard E2E API envelope."""
payload = self.get(path, location=location, params=params)
return payload.get("data", [])
def paginate(
self,
path: str,
location: str,
params: dict | None = None,
per_page: int = 100,
) -> list:
"""Iterate page_no/per_page style paginated list endpoints."""
all_items: list = []
page_no = 1
total_pages = 1
while page_no <= total_pages:
page_params = {"page_no": page_no, "per_page": per_page}
if params:
page_params.update(params)
payload = self.get(path, location=location, params=page_params)
data = payload.get("data", [])
if isinstance(data, list):
all_items.extend(data)
elif isinstance(data, dict):
all_items.extend(data.values())
total_pages = int(payload.get("total_page_number", page_no))
if not data:
break
page_no += 1
return all_items
@@ -0,0 +1,68 @@
import os
SENSITIVE_ARGUMENTS = frozenset({"--e2e-networks-api-key", "--e2e-networks-auth-token"})
def init_parser(self):
"""Init the E2E Networks Provider CLI parser."""
e2enetworks_parser = self.subparsers.add_parser(
"e2enetworks",
parents=[self.common_providers_parser],
help="E2E Networks Provider",
)
auth_group = e2enetworks_parser.add_argument_group("Authentication")
auth_group.add_argument(
"--e2e-networks-api-key",
nargs="?",
default=None,
metavar="E2E_NETWORKS_API_KEY",
help="E2E Networks API key. Use E2E_NETWORKS_API_KEY env var instead of passing directly.",
)
auth_group.add_argument(
"--e2e-networks-auth-token",
nargs="?",
default=None,
metavar="E2E_NETWORKS_AUTH_TOKEN",
help="E2E Networks auth token. Use E2E_NETWORKS_AUTH_TOKEN env var instead of passing directly.",
)
auth_group.add_argument(
"--e2e-networks-project-id",
nargs="?",
default=None,
metavar="E2E_NETWORKS_PROJECT_ID",
help="E2E Networks project ID. Use E2E_NETWORKS_PROJECT_ID env var instead of passing directly.",
)
regions_subparser = e2enetworks_parser.add_argument_group("Regions")
regions_subparser.add_argument(
"--region",
"--filter-region",
"-f",
nargs="+",
default=None,
metavar="REGION",
help="E2E Networks region(s) to scan (e.g. Delhi Chennai). If omitted, "
"uses E2E_NETWORKS_REGION or scans all regions (Delhi, Chennai).",
)
def validate_arguments(arguments) -> tuple[bool, str]:
"""Validate E2E Networks provider CLI arguments.
Only argument consistency is checked here so that listing operations such as
``--list-checks`` and ``--list-services`` run without credentials. Credential
presence (API key, auth token and project ID) is enforced later at provider
initialization, when an actual scan is performed.
"""
project_id = arguments.e2e_networks_project_id or os.getenv(
"E2E_NETWORKS_PROJECT_ID"
)
if project_id is not None:
try:
int(project_id)
except (TypeError, ValueError):
return False, "E2E Networks project ID must be an integer."
return True, ""
@@ -0,0 +1,27 @@
"""E2E Networks mutelist support for suppressing findings."""
from prowler.lib.check.models import CheckReportE2eNetworks
from prowler.lib.mutelist.mutelist import Mutelist
from prowler.lib.outputs.utils import unroll_dict, unroll_tags
class E2eNetworksMutelist(Mutelist):
"""Mutelist implementation for E2E Networks check findings."""
def is_finding_muted(self, **kwargs) -> bool:
"""Determine whether an E2E Networks finding is muted.
Args:
**kwargs: Keyword arguments; must include ``finding``.
Returns:
True if the finding matches a mutelist entry, otherwise False.
"""
finding: CheckReportE2eNetworks = kwargs["finding"]
return self.is_muted(
finding.resource_id,
finding.check_metadata.CheckID,
finding.location,
finding.resource_name,
unroll_dict(unroll_tags(finding.resource_tags)),
)
@@ -0,0 +1,19 @@
from prowler.providers.e2enetworks.e2enetworks_provider import E2enetworksProvider
from prowler.providers.e2enetworks.lib.api.client import E2eNetworksAPIClient
class E2eNetworksService:
"""Base class for E2E Networks services."""
def __init__(self, service: str, provider: E2enetworksProvider):
"""Initialize an E2E Networks service client.
Args:
service: Service name used for logging and configuration lookup.
provider: The active E2E Networks provider instance.
"""
self.provider = provider
self.audit_config = provider.audit_config
self.fixer_config = provider.fixer_config
self.service = service.lower() if not service.islower() else service
self.client = E2eNetworksAPIClient(provider.session)
+53
View File
@@ -0,0 +1,53 @@
from typing import Any
from pydantic import BaseModel, Field
from prowler.config.config import output_file_timestamp
from prowler.providers.common.models import ProviderOutputOptions
E2E_DEFAULT_LOCATIONS = ("Delhi", "Chennai")
E2E_BASE_URL = "https://api.e2enetworks.com/myaccount/api/v1"
class E2eNetworksSession(BaseModel):
"""E2E Networks API session information."""
api_key: str = Field(exclude=True, repr=False)
auth_token: str = Field(exclude=True, repr=False)
project_id: int
locations: list[str]
base_url: str = E2E_BASE_URL
http_session: Any = Field(default=None, exclude=True)
class E2eNetworksIdentityInfo(BaseModel):
"""E2E Networks identity and scoping information."""
project_id: int
locations: list[str]
class E2eNetworksOutputOptions(ProviderOutputOptions):
"""Customize output filenames for E2E Networks scans."""
def __init__(
self,
arguments: object,
bulk_checks_metadata: dict,
identity: E2eNetworksIdentityInfo,
) -> None:
"""Initialize E2E Networks output options.
Args:
arguments: Parsed CLI arguments for the scan.
bulk_checks_metadata: Loaded metadata for all checks in the scan.
identity: E2E Networks identity information used in output filenames.
"""
super().__init__(arguments, bulk_checks_metadata)
if (
not hasattr(arguments, "output_filename")
or arguments.output_filename is None
):
self.output_filename = f"prowler-output-e2enetworks-{identity.project_id}-{output_file_timestamp}"
else:
self.output_filename = arguments.output_filename
@@ -0,0 +1,4 @@
from prowler.providers.common.provider import Provider
from prowler.providers.e2enetworks.services.database.database_service import Database
database_client = Database(Provider.get_global_provider())
@@ -0,0 +1,36 @@
{
"Provider": "e2enetworks",
"CheckID": "database_cluster_backup_enabled",
"CheckTitle": "E2E Networks database clusters have automated backups enabled",
"CheckType": [],
"ServiceName": "database",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "NotDefined",
"ResourceGroup": "database",
"Description": "**E2E Networks database clusters** should have automated backups enabled so data can be restored after failures, corruption, or accidental deletion. Automated backups capture point-in-time copies of the cluster on a recurring schedule.",
"Risk": "Database clusters without backups are vulnerable to permanent **data loss** from hardware failures, corruption, or accidental deletion, with no way to recover to a prior state.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://docs.e2enetworks.com/api/myaccount/database/rds/"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": ""
},
"Recommendation": {
"Text": "Enable **automated backups** on every database cluster that does not currently have them configured, and validate that backups can be restored.",
"Url": "https://hub.prowler.com/check/database_cluster_backup_enabled"
}
},
"Categories": [
"resilience"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,24 @@
from prowler.lib.check.models import Check, CheckReportE2eNetworks
from prowler.providers.e2enetworks.services.database.database_client import (
database_client,
)
class database_cluster_backup_enabled(Check):
"""Check if E2E Networks database clusters have backups enabled."""
def execute(self) -> list[CheckReportE2eNetworks]:
findings = []
for cluster in database_client.clusters:
report = CheckReportE2eNetworks(metadata=self.metadata(), resource=cluster)
report.status = "PASS"
report.status_extended = (
f"Database cluster {cluster.name} has backups enabled."
)
if not cluster.backup_enabled:
report.status = "FAIL"
report.status_extended = (
f"Database cluster {cluster.name} does not have backups enabled."
)
findings.append(report)
return findings
@@ -0,0 +1,34 @@
{
"Provider": "e2enetworks",
"CheckID": "database_cluster_default_admin_username",
"CheckTitle": "E2E Networks database clusters do not use the default admin username",
"CheckType": [],
"ServiceName": "database",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "NotDefined",
"ResourceGroup": "database",
"Description": "**E2E Networks database clusters** should be configured with a custom administrative username instead of the well-known default, reducing the predictability of privileged credentials.",
"Risk": "Default admin usernames are publicly known, so they increase exposure to **credential guessing** and **brute-force attacks** against the privileged database account.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://docs.e2enetworks.com/api/myaccount/database/rds/"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": ""
},
"Recommendation": {
"Text": "Replace the default administrative username on database clusters with a unique, non-default account name and rotate the associated credentials.",
"Url": "https://hub.prowler.com/check/database_cluster_default_admin_username"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,22 @@
from prowler.lib.check.models import Check, CheckReportE2eNetworks
from prowler.providers.e2enetworks.services.database.database_client import (
database_client,
)
class database_cluster_default_admin_username(Check):
"""Check if E2E Networks database clusters do not use the default admin username."""
def execute(self) -> list[CheckReportE2eNetworks]:
findings = []
for cluster in database_client.clusters:
report = CheckReportE2eNetworks(metadata=self.metadata(), resource=cluster)
report.status = "PASS"
report.status_extended = f"Database cluster {cluster.name} does not use the default admin username."
if cluster.master_username.lower() == "admin":
report.status = "FAIL"
report.status_extended = (
f"Database cluster {cluster.name} uses the default admin username."
)
findings.append(report)
return findings
@@ -0,0 +1,36 @@
{
"Provider": "e2enetworks",
"CheckID": "database_cluster_ip_whitelist_configured",
"CheckTitle": "E2E Networks database clusters with public IPs have IP whitelisting configured",
"CheckType": [],
"ServiceName": "database",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "NotDefined",
"ResourceGroup": "database",
"Description": "**E2E Networks database clusters** that expose a public IP should restrict connectivity through an **IP whitelist** instead of accepting connections from any address (`0.0.0.0/0`).",
"Risk": "Publicly exposed database clusters without IP whitelisting accept connections from **any source on the internet**, greatly increasing the risk of unauthorized access and data exfiltration.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://docs.e2enetworks.com/api/myaccount/database/rds/"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": ""
},
"Recommendation": {
"Text": "Configure an **IP whitelist** on publicly exposed database clusters to allow only trusted source ranges, or move the cluster onto private networking.",
"Url": "https://hub.prowler.com/check/database_cluster_ip_whitelist_configured"
}
},
"Categories": [
"internet-exposed"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,24 @@
from prowler.lib.check.models import Check, CheckReportE2eNetworks
from prowler.providers.e2enetworks.services.database.database_client import (
database_client,
)
class database_cluster_ip_whitelist_configured(Check):
"""Check if E2E Networks database clusters with public IPs have IP whitelisting configured."""
def execute(self) -> list[CheckReportE2eNetworks]:
findings = []
for cluster in database_client.clusters:
if not cluster.master_has_public_ip:
continue
report = CheckReportE2eNetworks(metadata=self.metadata(), resource=cluster)
report.status = "PASS"
report.status_extended = (
f"Database cluster {cluster.name} has IP whitelisting configured."
)
if not cluster.whitelisted_ips:
report.status = "FAIL"
report.status_extended = f"Database cluster {cluster.name} has a public IP but no whitelisted IPs."
findings.append(report)
return findings
@@ -0,0 +1,36 @@
{
"Provider": "e2enetworks",
"CheckID": "database_cluster_public_ip_not_assigned",
"CheckTitle": "E2E Networks database cluster master nodes do not expose a public IP",
"CheckType": [],
"ServiceName": "database",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "NotDefined",
"ResourceGroup": "database",
"Description": "The **master node** of each **E2E Networks database cluster** should be reachable only over private networking and must not have a public IP address assigned.",
"Risk": "A **public database endpoint** is directly reachable from the internet, expanding the attack surface and increasing the risk of unauthorized access, brute-force attempts, and data exposure.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://docs.e2enetworks.com/api/myaccount/database/rds/"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": ""
},
"Recommendation": {
"Text": "Remove the **public IP** from the master node of database clusters and access them through private networking such as a VPC.",
"Url": "https://hub.prowler.com/check/database_cluster_public_ip_not_assigned"
}
},
"Categories": [
"internet-exposed"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,20 @@
from prowler.lib.check.models import Check, CheckReportE2eNetworks
from prowler.providers.e2enetworks.services.database.database_client import (
database_client,
)
class database_cluster_public_ip_not_assigned(Check):
"""Check if E2E Networks database clusters do not expose a public IP on the master node."""
def execute(self) -> list[CheckReportE2eNetworks]:
findings = []
for cluster in database_client.clusters:
report = CheckReportE2eNetworks(metadata=self.metadata(), resource=cluster)
report.status = "PASS"
report.status_extended = f"Database cluster {cluster.name} master node does not have a public IP."
if cluster.master_has_public_ip:
report.status = "FAIL"
report.status_extended = f"Database cluster {cluster.name} master node has a public IP assigned."
findings.append(report)
return findings
@@ -0,0 +1,34 @@
{
"Provider": "e2enetworks",
"CheckID": "database_cluster_running",
"CheckTitle": "E2E Networks database clusters are in RUNNING status",
"CheckType": [],
"ServiceName": "database",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "low",
"ResourceType": "NotDefined",
"ResourceGroup": "database",
"Description": "**E2E Networks database clusters** should be in the `RUNNING` state, confirming they are operational rather than stopped, failed, or stuck in a transitional status.",
"Risk": "Database clusters that are not `RUNNING` may indicate outages, failed provisioning, or misconfiguration that impacts **availability** and the ability to recover data.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://docs.e2enetworks.com/api/myaccount/database/rds/"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": ""
},
"Recommendation": {
"Text": "Start database clusters that are not in the `RUNNING` state and investigate the root cause of any stopped or failed clusters.",
"Url": "https://hub.prowler.com/check/database_cluster_running"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,20 @@
from prowler.lib.check.models import Check, CheckReportE2eNetworks
from prowler.providers.e2enetworks.services.database.database_client import (
database_client,
)
class database_cluster_running(Check):
"""Check if E2E Networks database clusters are in RUNNING status."""
def execute(self) -> list[CheckReportE2eNetworks]:
findings = []
for cluster in database_client.clusters:
report = CheckReportE2eNetworks(metadata=self.metadata(), resource=cluster)
report.status = "PASS"
report.status_extended = f"Database cluster {cluster.name} is running."
if cluster.status != "RUNNING":
report.status = "FAIL"
report.status_extended = f"Database cluster {cluster.name} is not running (status: {cluster.status})."
findings.append(report)
return findings
@@ -0,0 +1,36 @@
{
"Provider": "e2enetworks",
"CheckID": "database_cluster_ssl_enabled",
"CheckTitle": "E2E Networks database clusters have SSL enabled on the master node",
"CheckType": [],
"ServiceName": "database",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "NotDefined",
"ResourceGroup": "database",
"Description": "**E2E Networks database clusters** should require `SSL/TLS` on the master node so that client connections and credentials are encrypted **in transit**.",
"Risk": "Without `SSL/TLS`, database credentials and query data travel in **plaintext** and can be intercepted through man-in-the-middle or network eavesdropping attacks.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://docs.e2enetworks.com/api/myaccount/database/rds/"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": ""
},
"Recommendation": {
"Text": "Enable `SSL/TLS` on the master node of database clusters to encrypt all client connections in transit.",
"Url": "https://hub.prowler.com/check/database_cluster_ssl_enabled"
}
},
"Categories": [
"encryption"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,22 @@
from prowler.lib.check.models import Check, CheckReportE2eNetworks
from prowler.providers.e2enetworks.services.database.database_client import (
database_client,
)
class database_cluster_ssl_enabled(Check):
"""Check if E2E Networks database clusters have SSL enabled on the master node."""
def execute(self) -> list[CheckReportE2eNetworks]:
findings = []
for cluster in database_client.clusters:
report = CheckReportE2eNetworks(metadata=self.metadata(), resource=cluster)
report.status = "PASS"
report.status_extended = (
f"Database cluster {cluster.name} has SSL enabled on the master node."
)
if not cluster.master_ssl_enabled:
report.status = "FAIL"
report.status_extended = f"Database cluster {cluster.name} does not have SSL enabled on the master node."
findings.append(report)
return findings
@@ -0,0 +1,36 @@
{
"Provider": "e2enetworks",
"CheckID": "database_replica_public_ip_not_assigned",
"CheckTitle": "E2E Networks database read replicas do not have a public IP assigned",
"CheckType": [],
"ServiceName": "database",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "NotDefined",
"ResourceGroup": "database",
"Description": "**E2E Networks database read replicas** should be reachable only over private networking and must not have a public IP address assigned.",
"Risk": "Read replicas with **public IPs** are directly reachable from the internet, expanding database exposure and increasing the risk of unauthorized access to replicated data.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://docs.e2enetworks.com/api/myaccount/database/rds/"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": ""
},
"Recommendation": {
"Text": "Remove the **public IP** from database read replicas and route access through private networking.",
"Url": "https://hub.prowler.com/check/database_replica_public_ip_not_assigned"
}
},
"Categories": [
"internet-exposed"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,26 @@
from prowler.lib.check.models import Check, CheckReportE2eNetworks
from prowler.providers.e2enetworks.services.database.database_client import (
database_client,
)
class database_replica_public_ip_not_assigned(Check):
"""Check if E2E Networks database read replicas do not have a public IP assigned."""
def execute(self) -> list[CheckReportE2eNetworks]:
findings = []
for instance in database_client.instances:
if instance.role != "replica":
continue
report = CheckReportE2eNetworks(metadata=self.metadata(), resource=instance)
report.status = "PASS"
report.status_extended = (
f"Database replica {instance.name} does not have a public IP assigned."
)
if instance.has_public_ip:
report.status = "FAIL"
report.status_extended = (
f"Database replica {instance.name} has a public IP assigned."
)
findings.append(report)
return findings
@@ -0,0 +1,164 @@
from pydantic.v1 import BaseModel
from prowler.lib.logger import logger
from prowler.providers.e2enetworks.lib.service.service import E2eNetworksService
def _has_public_ip(public_ip_address: str | None) -> bool:
if not public_ip_address:
return False
value = str(public_ip_address).strip()
if not value or value.lower() in ("[]", "null", "none"):
return False
return True
class Database(E2eNetworksService):
"""Service class for E2E Networks DBaaS (RDS) resources."""
def __init__(self, provider):
super().__init__("database", provider)
self.clusters: list[DatabaseCluster] = []
self.instances: list[DatabaseInstance] = []
self._fetch_clusters()
def _fetch_clusters(self):
for location in self.provider.session.locations:
try:
cluster_list = self.client.get_data("/rds/cluster/", location=location)
if not isinstance(cluster_list, list):
continue
for item in cluster_list:
cluster_id = str(item.get("id", ""))
detail = self._get_cluster_detail(cluster_id, location)
merged = {**item, **detail}
master_node = merged.get("master_node", {}) or {}
database_info = master_node.get("database", {}) or {}
software = (
merged.get("software", {})
or master_node.get("plan", {}).get("software", {})
or {}
)
cluster = DatabaseCluster(
id=cluster_id,
name=merged.get("name", ""),
location=location,
status=merged.get("status", ""),
software_name=software.get("name", ""),
software_version=software.get("version", ""),
backup_enabled=bool(merged.get("backup_enabled", False)),
whitelisted_ips=merged.get("whitelisted_ips", []) or [],
master_ssl_enabled=bool(master_node.get("ssl", False)),
master_public_ip=master_node.get("public_ip_address"),
master_username=database_info.get("username", ""),
master_has_public_ip=_has_public_ip(
master_node.get("public_ip_address")
),
)
self.clusters.append(cluster)
self.instances.append(
DatabaseInstance(
id=str(master_node.get("instance_id", cluster_id)),
name=master_node.get("node_name", merged.get("name", "")),
cluster_id=cluster_id,
cluster_name=cluster.name,
location=location,
role="master",
public_ip_address=master_node.get("public_ip_address"),
has_public_ip=_has_public_ip(
master_node.get("public_ip_address")
),
ssl_enabled=bool(master_node.get("ssl", False)),
username=database_info.get("username", ""),
)
)
for slave in merged.get("slave_nodes", []) or []:
if not isinstance(slave, dict):
continue
slave_db = slave.get("database", {}) or {}
self.instances.append(
DatabaseInstance(
id=str(slave.get("instance_id", "")),
name=slave.get("node_name", ""),
cluster_id=cluster_id,
cluster_name=cluster.name,
location=location,
role="replica",
public_ip_address=slave.get("public_ip_address"),
has_public_ip=_has_public_ip(
slave.get("public_ip_address")
),
ssl_enabled=bool(slave.get("ssl", False)),
username=slave_db.get("username", ""),
)
)
except Exception as error:
logger.error(
f"database - Error fetching clusters in {location} -- "
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
def _get_cluster_detail(self, cluster_id: str, location: str) -> dict:
if not cluster_id:
return {}
try:
data = self.client.get_data(
f"/rds/cluster/{cluster_id}/",
location=location,
)
return data if isinstance(data, dict) else {}
except Exception as error:
logger.error(
f"database - Error fetching cluster detail {cluster_id} -- "
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
return {}
class DatabaseCluster(BaseModel):
id: str
name: str
location: str
status: str = ""
software_name: str = ""
software_version: str = ""
backup_enabled: bool = False
whitelisted_ips: list = []
master_ssl_enabled: bool = False
master_public_ip: str | None = None
master_username: str = ""
master_has_public_ip: bool = False
@property
def resource_id(self) -> str:
return self.id
@property
def resource_name(self) -> str:
return self.name
class DatabaseInstance(BaseModel):
id: str
name: str
cluster_id: str
cluster_name: str
location: str
role: str
public_ip_address: str | None = None
has_public_ip: bool = False
ssl_enabled: bool = False
username: str = ""
@property
def resource_id(self) -> str:
return self.id
@property
def resource_name(self) -> str:
return self.name
@@ -0,0 +1,36 @@
{
"Provider": "e2enetworks",
"CheckID": "loadbalancer_alb_https_uses_ssl_certificate",
"CheckTitle": "E2E Networks ALB HTTPS load balancers use an SSL certificate",
"CheckType": [],
"ServiceName": "loadbalancer",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "NotDefined",
"ResourceGroup": "network",
"Description": "**E2E Networks application load balancers** serving `HTTPS` traffic should have a valid **SSL certificate** configured so client connections are encrypted and authenticated.",
"Risk": "`HTTPS` load balancers without a valid **SSL certificate** expose traffic to interception and downgrade attacks, weakening **confidentiality** and **integrity** for client connections.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://docs.e2enetworks.com/api/myaccount/compute/load-balancer/"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": ""
},
"Recommendation": {
"Text": "Attach a valid **SSL certificate** to every load balancer that serves `HTTPS` traffic.",
"Url": "https://hub.prowler.com/check/loadbalancer_alb_https_uses_ssl_certificate"
}
},
"Categories": [
"encryption"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,25 @@
from prowler.lib.check.models import Check, CheckReportE2eNetworks
from prowler.providers.e2enetworks.services.loadbalancer.loadbalancer_client import (
loadbalancer_client,
)
class loadbalancer_alb_https_uses_ssl_certificate(Check):
"""Check that HTTPS load balancers have an SSL certificate configured."""
def execute(self) -> list[CheckReportE2eNetworks]:
findings = []
for lb in loadbalancer_client.load_balancers:
if not lb.is_alb_https:
continue
report = CheckReportE2eNetworks(metadata=self.metadata(), resource=lb)
report.status = "PASS"
report.status_extended = (
f"Load balancer {lb.name} uses an SSL certificate for HTTPS traffic."
)
if not lb.ssl_certificate_id:
report.status = "FAIL"
report.status_extended = f"Load balancer {lb.name} does not have an SSL certificate configured for HTTPS traffic."
findings.append(report)
return findings
@@ -0,0 +1,36 @@
{
"Provider": "e2enetworks",
"CheckID": "loadbalancer_backend_health_check_enabled",
"CheckTitle": "E2E Networks ALB load balancers have backend health checks enabled",
"CheckType": [],
"ServiceName": "loadbalancer",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "NotDefined",
"ResourceGroup": "network",
"Description": "**E2E Networks application load balancers** should have `HTTP` **health checks** configured for their backends so traffic is only routed to healthy nodes.",
"Risk": "Load balancers without backend **health checks** may route traffic to failed or unhealthy nodes, causing outages and masking active compromise on degraded backends.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://docs.e2enetworks.com/api/myaccount/compute/load-balancer/"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": ""
},
"Recommendation": {
"Text": "Configure `HTTP` **health checks** for ALB backends so unhealthy nodes are automatically removed from rotation.",
"Url": "https://hub.prowler.com/check/loadbalancer_backend_health_check_enabled"
}
},
"Categories": [
"resilience"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,25 @@
from prowler.lib.check.models import Check, CheckReportE2eNetworks
from prowler.providers.e2enetworks.services.loadbalancer.loadbalancer_client import (
loadbalancer_client,
)
class loadbalancer_backend_health_check_enabled(Check):
"""Check that ALB load balancers have backend health checks configured."""
def execute(self) -> list[CheckReportE2eNetworks]:
findings = []
for lb in loadbalancer_client.load_balancers:
if not lb.is_alb:
continue
report = CheckReportE2eNetworks(metadata=self.metadata(), resource=lb)
report.status = "PASS"
report.status_extended = (
f"Load balancer {lb.name} has backend health checks configured."
)
if not lb.has_backend_health_check:
report.status = "FAIL"
report.status_extended = f"Load balancer {lb.name} does not have backend health checks configured."
findings.append(report)
return findings
@@ -0,0 +1,34 @@
{
"Provider": "e2enetworks",
"CheckID": "loadbalancer_bitninja_enabled",
"CheckTitle": "E2E Networks load balancers have BitNinja protection enabled",
"CheckType": [],
"ServiceName": "loadbalancer",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "low",
"ResourceType": "NotDefined",
"ResourceGroup": "network",
"Description": "**E2E Networks load balancers** should have **BitNinja** protection enabled to provide web application firewall coverage on public endpoints.",
"Risk": "Load balancers without **BitNinja** protection have reduced **WAF** coverage, increasing exposure to automated attacks and malicious traffic against public endpoints.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://docs.e2enetworks.com/api/myaccount/compute/load-balancer/"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": ""
},
"Recommendation": {
"Text": "Enable **BitNinja** protection on load balancer appliances to strengthen web application firewall coverage.",
"Url": "https://hub.prowler.com/check/loadbalancer_bitninja_enabled"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,22 @@
from prowler.lib.check.models import Check, CheckReportE2eNetworks
from prowler.providers.e2enetworks.services.loadbalancer.loadbalancer_client import (
loadbalancer_client,
)
class loadbalancer_bitninja_enabled(Check):
"""Check that load balancers have BitNinja protection enabled."""
def execute(self) -> list[CheckReportE2eNetworks]:
findings = []
for lb in loadbalancer_client.load_balancers:
report = CheckReportE2eNetworks(metadata=self.metadata(), resource=lb)
report.status = "PASS"
report.status_extended = (
f"Load balancer {lb.name} has BitNinja protection enabled."
)
if not lb.enable_bitninja:
report.status = "FAIL"
report.status_extended = f"Load balancer {lb.name} does not have BitNinja protection enabled."
findings.append(report)
return findings
@@ -0,0 +1,6 @@
from prowler.providers.common.provider import Provider
from prowler.providers.e2enetworks.services.loadbalancer.loadbalancer_service import (
LoadBalancers,
)
loadbalancer_client = LoadBalancers(Provider.get_global_provider())
@@ -0,0 +1,96 @@
from pydantic.v1 import BaseModel
from prowler.lib.logger import logger
from prowler.providers.e2enetworks.lib.service.service import E2eNetworksService
class LoadBalancers(E2eNetworksService):
"""Service class for E2E Networks load balancers."""
def __init__(self, provider):
super().__init__("loadbalancer", provider)
self.load_balancers: list[LoadBalancer] = []
self._fetch_loadbalancers()
def _fetch_loadbalancers(self):
for location in self.provider.session.locations:
try:
appliances = self.client.paginate(
"/appliances/",
location=location,
)
for item in appliances:
context = self._extract_context(item)
node_detail = item.get("node_detail", {}) or {}
self.load_balancers.append(
LoadBalancer(
id=str(item.get("id", "")),
name=item.get("name", ""),
location=location,
status=item.get("status", ""),
lb_mode=context.get("lb_mode", ""),
lb_port=str(context.get("lb_port", "")),
enable_bitninja=bool(context.get("enable_bitninja", False)),
ssl_certificate_id=self._get_ssl_certificate_id(context),
backends=context.get("backends", []) or [],
public_ip=node_detail.get("public_ip", ""),
)
)
except Exception as error:
logger.error(
f"loadbalancer - Error fetching appliances in {location} -- "
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
@staticmethod
def _extract_context(item: dict) -> dict:
instances = item.get("appliance_instance", []) or []
if not instances:
return {}
return instances[0].get("context", {}) or {}
@staticmethod
def _get_ssl_certificate_id(context: dict) -> str | None:
ssl_context = context.get("ssl_context", {}) or {}
certificate_id = ssl_context.get("ssl_certificate_id")
if certificate_id in (None, "", 0):
return None
return str(certificate_id)
class LoadBalancer(BaseModel):
id: str
name: str
location: str
status: str = ""
lb_mode: str = ""
lb_port: str = ""
enable_bitninja: bool = False
ssl_certificate_id: str | None = None
backends: list = []
public_ip: str = ""
@property
def resource_id(self) -> str:
return self.id
@property
def resource_name(self) -> str:
return self.name
@property
def is_alb(self) -> bool:
mode = self.lb_mode.upper()
return mode in ("HTTP", "HTTPS", "BOTH")
@property
def is_alb_https(self) -> bool:
mode = self.lb_mode.upper()
return mode in ("HTTPS", "BOTH")
@property
def has_backend_health_check(self) -> bool:
for backend in self.backends:
if isinstance(backend, dict) and backend.get("http_check"):
return True
return False
@@ -0,0 +1,4 @@
from prowler.providers.common.provider import Provider
from prowler.providers.e2enetworks.services.network.network_service import Network
network_client = Network(Provider.get_global_provider())
@@ -0,0 +1,34 @@
{
"Provider": "e2enetworks",
"CheckID": "network_reserveip_floating_ip_unattached",
"CheckTitle": "E2E Networks floating IPs are attached to nodes",
"CheckType": [],
"ServiceName": "network",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "NotDefined",
"ResourceGroup": "network",
"Description": "**E2E Networks floating (reserved) IP addresses** should be attached to a node instead of being left unassigned, since idle floating IPs can be reassigned unexpectedly.",
"Risk": "**Unattached floating IPs** may be reassigned unexpectedly to other resources, disrupting secure routing assumptions and potentially exposing services.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://docs.e2enetworks.com/api/myaccount/network/reserve-ip/"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": ""
},
"Recommendation": {
"Text": "Attach unassigned **floating IPs** to the intended node, or release them if they are no longer needed.",
"Url": "https://hub.prowler.com/check/network_reserveip_floating_ip_unattached"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,24 @@
from prowler.lib.check.models import Check, CheckReportE2eNetworks
from prowler.providers.e2enetworks.services.network.network_client import network_client
class network_reserveip_floating_ip_unattached(Check):
"""Check if E2E Networks floating IPs are attached to nodes."""
def execute(self) -> list[CheckReportE2eNetworks]:
findings = []
for ip in network_client.reserved_ips:
if ip.reserved_type != "FloatingIP":
continue
report = CheckReportE2eNetworks(metadata=self.metadata(), resource=ip)
report.status = "PASS"
report.status_extended = (
f"Floating IP {ip.ip_address} is attached to node(s)."
)
if ip.status != "Attached" or ip.floating_ip_attached_nodes_count == 0:
report.status = "FAIL"
report.status_extended = (
f"Floating IP {ip.ip_address} is not attached to any node."
)
findings.append(report)
return findings
@@ -0,0 +1,36 @@
{
"Provider": "e2enetworks",
"CheckID": "network_reserveip_orphaned_public_ip",
"CheckTitle": "E2E Networks public or addon IPs are attached to a resource",
"CheckType": [],
"ServiceName": "network",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "NotDefined",
"ResourceGroup": "network",
"Description": "**E2E Networks public and addon reserved IP addresses** should be attached to a resource, identifying orphaned addresses that remain internet-reachable without an owner.",
"Risk": "**Orphaned public IPs** remain internet-reachable resources that can be misused or reassigned without ownership controls, expanding the attack surface.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://docs.e2enetworks.com/api/myaccount/network/reserve-ip/"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": ""
},
"Recommendation": {
"Text": "Attach orphaned **public or addon IPs** to a resource, or delete them to remove unused internet-reachable addresses.",
"Url": "https://hub.prowler.com/check/network_reserveip_orphaned_public_ip"
}
},
"Categories": [
"internet-exposed"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,24 @@
from prowler.lib.check.models import Check, CheckReportE2eNetworks
from prowler.providers.e2enetworks.services.network.network_client import network_client
class network_reserveip_orphaned_public_ip(Check):
"""Check if E2E Networks public or addon IPs are attached."""
def execute(self) -> list[CheckReportE2eNetworks]:
findings = []
for ip in network_client.reserved_ips:
if ip.reserved_type not in ("PublicIP", "AddonIP"):
continue
report = CheckReportE2eNetworks(metadata=self.metadata(), resource=ip)
report.status = "PASS"
report.status_extended = (
f"Reserved IP {ip.ip_address} is attached to a resource."
)
if ip.status != "Attached" or ip.vm_id is None:
report.status = "FAIL"
report.status_extended = (
f"Reserved IP {ip.ip_address} is orphaned (status: {ip.status})."
)
findings.append(report)
return findings
@@ -0,0 +1,158 @@
from pydantic.v1 import BaseModel
from prowler.lib.logger import logger
from prowler.providers.e2enetworks.lib.service.service import E2eNetworksService
class Network(E2eNetworksService):
"""Service class for E2E Networks network resources."""
def __init__(self, provider):
super().__init__("network", provider)
self.vpcs: list[Vpc] = []
self.reserved_ips: list[ReservedIp] = []
self.vpc_tunnels: list[VpcTunnel] = []
self._fetch_vpcs()
self._fetch_reserved_ips()
self._fetch_vpc_tunnels()
def _fetch_vpcs(self):
for location in self.provider.session.locations:
try:
vpcs = self.client.paginate("/vpc/list/", location=location)
if not isinstance(vpcs, list):
continue
for item in vpcs:
gateway_node = item.get("gateway_node", {}) or {}
self.vpcs.append(
Vpc(
network_id=str(item.get("network_id", "")),
name=item.get("name", ""),
location=location,
is_active=bool(item.get("is_active", False)),
state=item.get("state", ""),
ipv4_cidr=item.get("ipv4_cidr", ""),
vm_count=int(item.get("vm_count", 0)),
gateway_node_id=str(gateway_node.get("node_id", "")),
gateway_public_ip=gateway_node.get("ip_address_public", ""),
)
)
except Exception as error:
logger.error(
f"network - Error fetching VPCs in {location} -- "
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
def _fetch_reserved_ips(self):
for location in self.provider.session.locations:
try:
ips = self.client.get_data("/reserve_ips/", location=location)
if not isinstance(ips, list):
continue
for item in ips:
attached_nodes = item.get("floating_ip_attached_nodes", []) or []
self.reserved_ips.append(
ReservedIp(
reserve_id=str(item.get("reserve_id", "")),
ip_address=item.get("ip_address", ""),
location=location,
status=item.get("status", ""),
reserved_type=item.get("reserved_type", ""),
vm_id=item.get("vm_id"),
floating_ip_attached_nodes_count=len(attached_nodes),
)
)
except Exception as error:
logger.error(
f"network - Error fetching reserved IPs in {location} -- "
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
def _fetch_vpc_tunnels(self):
for vpc in self.vpcs:
if not vpc.network_id:
continue
try:
tunnels = self.client.get_data(
f"/vpc/tunnels/{vpc.network_id}/",
location=vpc.location,
)
if not isinstance(tunnels, list):
continue
for item in tunnels:
self.vpc_tunnels.append(
VpcTunnel(
id=str(item.get("id", "")),
name=item.get("name", ""),
location=vpc.location,
local_vpc_network_id=vpc.network_id,
local_vpc_name=vpc.name,
status=item.get("status", ""),
is_peer_vpc_external=bool(
item.get("is_peer_vpc_external", False)
),
)
)
except Exception as error:
logger.error(
f"network - Error fetching tunnels for VPC {vpc.network_id} -- "
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
class Vpc(BaseModel):
network_id: str
name: str
location: str
is_active: bool = False
state: str = ""
ipv4_cidr: str = ""
vm_count: int = 0
gateway_node_id: str = ""
gateway_public_ip: str = ""
@property
def resource_id(self) -> str:
return self.network_id
@property
def resource_name(self) -> str:
return self.name
class ReservedIp(BaseModel):
reserve_id: str
ip_address: str
location: str
status: str = ""
reserved_type: str = ""
vm_id: int | None = None
floating_ip_attached_nodes_count: int = 0
@property
def resource_id(self) -> str:
return self.reserve_id
@property
def resource_name(self) -> str:
return self.ip_address
class VpcTunnel(BaseModel):
id: str
name: str
location: str
local_vpc_network_id: str
local_vpc_name: str
status: str = ""
is_peer_vpc_external: bool = False
@property
def resource_id(self) -> str:
return self.id
@property
def resource_name(self) -> str:
return self.name
@@ -0,0 +1,34 @@
{
"Provider": "e2enetworks",
"CheckID": "network_vpc_has_attached_nodes",
"CheckTitle": "E2E Networks VPCs have attached nodes",
"CheckType": [],
"ServiceName": "network",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "low",
"ResourceType": "NotDefined",
"ResourceGroup": "network",
"Description": "**E2E Networks VPCs** should have at least one node attached, identifying unused network segments that may indicate misconfiguration or forgotten resources.",
"Risk": "**VPCs without attached nodes** may indicate unused network segments or misconfiguration that weakens **network segmentation** and increases management overhead.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://docs.e2enetworks.com/api/myaccount/network/vpc/"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": ""
},
"Recommendation": {
"Text": "Attach nodes to VPCs that are in use, or remove empty VPCs that are no longer required.",
"Url": "https://hub.prowler.com/check/network_vpc_has_attached_nodes"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,20 @@
from prowler.lib.check.models import Check, CheckReportE2eNetworks
from prowler.providers.e2enetworks.services.network.network_client import network_client
class network_vpc_has_attached_nodes(Check):
"""Check if E2E Networks VPCs have attached nodes."""
def execute(self) -> list[CheckReportE2eNetworks]:
findings = []
for vpc in network_client.vpcs:
report = CheckReportE2eNetworks(metadata=self.metadata(), resource=vpc)
report.status = "PASS"
report.status_extended = (
f"VPC {vpc.name} has {vpc.vm_count} attached node(s)."
)
if vpc.vm_count <= 0:
report.status = "FAIL"
report.status_extended = f"VPC {vpc.name} has no attached nodes."
findings.append(report)
return findings
@@ -0,0 +1,34 @@
{
"Provider": "e2enetworks",
"CheckID": "network_vpc_is_active",
"CheckTitle": "E2E Networks VPCs are active",
"CheckType": [],
"ServiceName": "network",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "NotDefined",
"ResourceGroup": "network",
"Description": "**E2E Networks VPCs** should be in the `active` state so that attached workloads retain expected connectivity and network isolation.",
"Risk": "**Inactive VPCs** can cause connectivity failures and may leave workloads without the expected **network isolation** boundaries.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://docs.e2enetworks.com/api/myaccount/network/vpc/"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": ""
},
"Recommendation": {
"Text": "Activate VPCs that are `inactive` so dependent workloads keep their expected connectivity and isolation.",
"Url": "https://hub.prowler.com/check/network_vpc_is_active"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,20 @@
from prowler.lib.check.models import Check, CheckReportE2eNetworks
from prowler.providers.e2enetworks.services.network.network_client import network_client
class network_vpc_is_active(Check):
"""Check if E2E Networks VPCs are active."""
def execute(self) -> list[CheckReportE2eNetworks]:
findings = []
for vpc in network_client.vpcs:
report = CheckReportE2eNetworks(metadata=self.metadata(), resource=vpc)
report.status = "PASS"
report.status_extended = f"VPC {vpc.name} is active."
if not vpc.is_active or vpc.state != "Active":
report.status = "FAIL"
report.status_extended = (
f"VPC {vpc.name} is not active (state: {vpc.state})."
)
findings.append(report)
return findings
@@ -0,0 +1,36 @@
{
"Provider": "e2enetworks",
"CheckID": "network_vpc_peering_external_peer_disabled",
"CheckTitle": "E2E Networks VPC peering does not use external peers",
"CheckType": [],
"ServiceName": "network",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "NotDefined",
"ResourceGroup": "network",
"Description": "**E2E Networks VPC peering connections** should be established only with internal networks and must not extend trust to external peers outside the account.",
"Risk": "**VPC peering with external peers** expands trust boundaries and can expose private networks to third parties, enabling lateral movement across accounts.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://docs.e2enetworks.com/api/myaccount/network/vpc/"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": ""
},
"Recommendation": {
"Text": "Remove **VPC peering** tunnels that connect to external peers to keep private networks within trusted boundaries.",
"Url": "https://hub.prowler.com/check/network_vpc_peering_external_peer_disabled"
}
},
"Categories": [
"trust-boundaries"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,22 @@
from prowler.lib.check.models import Check, CheckReportE2eNetworks
from prowler.providers.e2enetworks.services.network.network_client import network_client
class network_vpc_peering_external_peer_disabled(Check):
"""Check if E2E Networks VPC peering does not use external peers."""
def execute(self) -> list[CheckReportE2eNetworks]:
findings = []
for tunnel in network_client.vpc_tunnels:
report = CheckReportE2eNetworks(metadata=self.metadata(), resource=tunnel)
report.status = "PASS"
report.status_extended = (
f"VPC peering {tunnel.name} does not use an external peer VPC."
)
if tunnel.is_peer_vpc_external:
report.status = "FAIL"
report.status_extended = (
f"VPC peering {tunnel.name} uses an external peer VPC."
)
findings.append(report)
return findings
@@ -0,0 +1,36 @@
{
"Provider": "e2enetworks",
"CheckID": "node_accidental_protection_enabled",
"CheckTitle": "E2E Networks nodes have accidental protection enabled",
"CheckType": [],
"ServiceName": "node",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "NotDefined",
"ResourceGroup": "compute",
"Description": "**E2E Networks nodes** should have **accidental protection** enabled to guard against unintended deletion or modification of compute instances.",
"Risk": "Nodes without **accidental protection** are easier to delete or modify unintentionally, increasing the risk of **service disruption** and unplanned exposure of compute resources.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://docs.e2enetworks.com/api/myaccount/compute/nodes/"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": ""
},
"Recommendation": {
"Text": "Enable **accidental protection** on nodes to prevent unintended deletion or modification.",
"Url": "https://hub.prowler.com/check/node_accidental_protection_enabled"
}
},
"Categories": [
"resilience"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,22 @@
from prowler.lib.check.models import Check, CheckReportE2eNetworks
from prowler.providers.e2enetworks.services.node.node_client import node_client
class node_accidental_protection_enabled(Check):
"""Check if E2E Networks nodes have accidental protection enabled."""
def execute(self) -> list[CheckReportE2eNetworks]:
findings = []
for node in node_client.nodes:
report = CheckReportE2eNetworks(metadata=self.metadata(), resource=node)
report.status = "PASS"
report.status_extended = (
f"Node {node.name} has accidental protection enabled."
)
if not node.is_accidental_protection:
report.status = "FAIL"
report.status_extended = (
f"Node {node.name} does not have accidental protection enabled."
)
findings.append(report)
return findings
@@ -0,0 +1,4 @@
from prowler.providers.common.provider import Provider
from prowler.providers.e2enetworks.services.node.node_service import Nodes
node_client = Nodes(Provider.get_global_provider())
@@ -0,0 +1,34 @@
{
"Provider": "e2enetworks",
"CheckID": "node_compliance_enabled",
"CheckTitle": "E2E Networks nodes have compliance mode enabled",
"CheckType": [],
"ServiceName": "node",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "NotDefined",
"ResourceGroup": "compute",
"Description": "**E2E Networks nodes** should run with **compliance mode** enabled so required security controls are enforced and workload activity remains auditable.",
"Risk": "Nodes without **compliance mode** may not enforce required security controls, increasing misconfiguration risk and weakening the **auditability** of compute workloads.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://docs.e2enetworks.com/api/myaccount/compute/nodes/"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": ""
},
"Recommendation": {
"Text": "Enable **compliance mode** on nodes to enforce required security controls and maintain auditability.",
"Url": "https://hub.prowler.com/check/node_compliance_enabled"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,20 @@
from prowler.lib.check.models import Check, CheckReportE2eNetworks
from prowler.providers.e2enetworks.services.node.node_client import node_client
class node_compliance_enabled(Check):
"""Check if E2E Networks nodes have compliance mode enabled."""
def execute(self) -> list[CheckReportE2eNetworks]:
findings = []
for node in node_client.nodes:
report = CheckReportE2eNetworks(metadata=self.metadata(), resource=node)
report.status = "PASS"
report.status_extended = f"Node {node.name} has compliance mode enabled."
if not node.is_node_compliance:
report.status = "FAIL"
report.status_extended = (
f"Node {node.name} does not have compliance mode enabled."
)
findings.append(report)
return findings
@@ -0,0 +1,36 @@
{
"Provider": "e2enetworks",
"CheckID": "node_encryption_enabled",
"CheckTitle": "E2E Networks nodes have disk encryption enabled",
"CheckType": [],
"ServiceName": "node",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "NotDefined",
"ResourceGroup": "compute",
"Description": "**E2E Networks nodes** should have **disk encryption** enabled so data stored on node volumes and snapshots is protected **at rest**.",
"Risk": "**Unencrypted nodes** increase the risk of **data exposure** if disks or snapshots are accessed outside normal operating controls.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://docs.e2enetworks.com/api/myaccount/compute/nodes/"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": ""
},
"Recommendation": {
"Text": "Enable **disk encryption** on nodes to protect data at rest on volumes and snapshots.",
"Url": "https://hub.prowler.com/check/node_encryption_enabled"
}
},
"Categories": [
"encryption"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}

Some files were not shown because too many files have changed in this diff Show More