Compare commits

..
Author SHA1 Message Date
github-actions 93b884a513 chore(release): 3.16.11 2024-07-04 16:56:31 +00:00
220 changed files with 1297 additions and 3626 deletions
+1 -5
View File
@@ -1,5 +1 @@
* @prowler-cloud/sdk @prowler-cloud/detection-and-remediation
# To protect a repository fully against unauthorized changes, you also need to define an owner for the CODEOWNERS file itself.
# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners#codeowners-and-branch-protection
/.github/ @prowler-cloud/sdk
* @prowler-cloud/prowler-oss @prowler-cloud/prowler-dev
@@ -43,7 +43,7 @@ jobs:
runs-on: ubuntu-latest
outputs:
prowler_version_major: ${{ steps.get-prowler-version.outputs.PROWLER_VERSION_MAJOR }}
prowler_version: ${{ steps.get-prowler-version.outputs.PROWLER_VERSION }}
prowler_version: ${{ steps.update-prowler-version.outputs.PROWLER_VERSION }}
env:
POETRY_VIRTUALENVS_CREATE: "false"
@@ -65,8 +65,6 @@ jobs:
id: get-prowler-version
run: |
PROWLER_VERSION="$(poetry version -s 2>/dev/null)"
echo "PROWLER_VERSION=${PROWLER_VERSION}" >> "${GITHUB_ENV}"
echo "PROWLER_VERSION=${PROWLER_VERSION}" >> "${GITHUB_OUTPUT}"
# Store prowler version major just for the release
PROWLER_VERSION_MAJOR="${PROWLER_VERSION%%.*}"
@@ -91,6 +89,15 @@ jobs:
;;
esac
- name: Update Prowler version (release)
id: update-prowler-version
if: github.event_name == 'release'
run: |
PROWLER_VERSION="${{ github.event.release.tag_name }}"
poetry version "${PROWLER_VERSION}"
echo "PROWLER_VERSION=${PROWLER_VERSION}" >> "${GITHUB_ENV}"
echo "PROWLER_VERSION=${PROWLER_VERSION}" >> "${GITHUB_OUTPUT}"
- name: Login to DockerHub
uses: docker/login-action@v3
with:
+1 -1
View File
@@ -11,7 +11,7 @@ jobs:
with:
fetch-depth: 0
- name: TruffleHog OSS
uses: trufflesecurity/trufflehog@v3.82.2
uses: trufflesecurity/trufflehog@v3.79.0
with:
path: ./
base: ${{ github.event.repository.default_branch }}
+2 -2
View File
@@ -20,7 +20,7 @@ jobs:
- uses: actions/checkout@v4
- name: Test if changes are in not ignored paths
id: are-non-ignored-files-changed
uses: tj-actions/changed-files@v45
uses: tj-actions/changed-files@v44
with:
files: ./**
files_ignore: |
@@ -73,7 +73,7 @@ jobs:
- name: Safety
if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true'
run: |
poetry run safety check --ignore 70612
poetry run safety check --ignore 67599 --ignore 70612
- name: Vulture
if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true'
run: |
+21
View File
@@ -40,6 +40,7 @@ jobs:
- name: Install dependencies
run: |
pipx install poetry
pipx inject poetry poetry-bumpversion
- name: Setup Python
uses: actions/setup-python@v5
@@ -47,6 +48,10 @@ jobs:
python-version: ${{ env.PYTHON_VERSION }}
cache: ${{ env.CACHE }}
- name: Update Poetry and config version
run: |
poetry version ${{ env.RELEASE_TAG }}
- name: Import GPG key
uses: crazy-max/ghaction-import-gpg@v6
with:
@@ -55,6 +60,22 @@ jobs:
git_user_signingkey: true
git_commit_gpgsign: true
- name: Push updated version to the release tag
run: |
# Configure Git
git config user.name "github-actions"
git config user.email "${{ env.GIT_COMMITTER_EMAIL }}"
# Add the files with the version changed
git add prowler/config/config.py pyproject.toml
git commit -m "chore(release): ${{ env.RELEASE_TAG }}" --no-verify -S
# Replace the tag with the version updated
git tag -fa ${{ env.RELEASE_TAG }} -m "chore(release): ${{ env.RELEASE_TAG }}" --sign
# Push the tag
git push -f origin ${{ env.RELEASE_TAG }}
- name: Build Prowler package
run: |
poetry build
@@ -50,7 +50,7 @@ jobs:
# Create pull request
- name: Create Pull Request
uses: peter-evans/create-pull-request@v7
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.PROWLER_ACCESS_TOKEN }}
commit-message: "feat(regions_update): Update regions for AWS services."
+1 -1
View File
@@ -96,7 +96,7 @@ repos:
- id: safety
name: safety
description: "Safety is a tool that checks your installed dependencies for known security vulnerabilities"
entry: bash -c 'safety check --ignore 70612'
entry: bash -c 'safety check --ignore 67599 --ignore 70612'
language: system
- id: vulture
+1 -1
View File
@@ -230,7 +230,7 @@ Each Prowler check has metadata associated which is stored at the same level of
# Severity holds the check's severity, always in lowercase (critical, high, medium, low or informational)
"Severity": "critical",
# ResourceType only for AWS, holds the type from here
# https://docs.aws.amazon.com/securityhub/latest/userguide/asff-resources.html
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html
"ResourceType": "Other",
# Description holds the title of the check, for now is the same as CheckTitle
"Description": "Ensure there are no EC2 AMIs set as Public.",
-1
View File
@@ -81,7 +81,6 @@ aws:
max_ec2_instance_age_in_days: 180
# AWS VPC Configuration (vpc_endpoint_connections_trust_boundaries, vpc_endpoint_services_allowed_principals_trust_boundaries)
# AWS SSM Configuration (aws.ssm_documents_set_as_public)
# Single account environment: No action required. The AWS account number will be automatically added by the checks.
# Multi account environment: Any additional trusted account number should be added as a space separated list, e.g.
# trusted_account_ids : ["123456789012", "098765432109", "678901234567"]
+1 -2
View File
@@ -36,11 +36,10 @@ If EBS default encyption is not enabled, sensitive information at rest is not pr
- `ec2_ebs_default_encryption`
If your Security groups are not properly configured the attack surface is increased, nonetheless, Prowler will detect those security groups that are being used (they are attached) to only notify those that are being used. This logic applies to the 15 checks related to open ports in security groups, the check for the default security group and for the security groups that allow ingress and egress traffic.
If your Security groups are not properly configured the attack surface is increased, nonetheless, Prowler will detect those security groups that are being used (they are attached) to only notify those that are being used. This logic applies to the 15 checks related to open ports in security groups and the check for the default security group.
- `ec2_securitygroup_allow_ingress_from_internet_to_port_X` (15 checks)
- `ec2_securitygroup_default_restrict_traffic`
- `ec2_securitygroup_allow_wide_open_public_ipv4`
Prowler will also check for used Network ACLs to only alerts those with open ports that are being used.
Generated
+282 -344
View File
File diff suppressed because it is too large Load Diff
+1 -28
View File
@@ -3,16 +3,10 @@
import os
import sys
import threading
from colorama import Fore, Style
from prowler.config.config import get_available_update
from prowler.lib.banner import (
print_banner,
print_prowler_cloud_banner,
print_update_notice,
)
from prowler.lib.banner import print_banner
from prowler.lib.check.check import (
bulk_load_checks_metadata,
bulk_load_compliance_frameworks,
@@ -79,18 +73,6 @@ def prowler():
compliance_framework = args.compliance
custom_checks_metadata_file = args.custom_checks_metadata_file
# Check in the background whether a newer Prowler release is available;
# the result is printed at the end of the scan so the check never adds
# latency. Skipped with --no-banner/--only-logs and via
# PROWLER_NO_VERSION_CHECK/DO_NOT_TRACK (handled in get_available_update).
available_update = {}
update_check_thread = threading.Thread(
target=lambda: available_update.update(latest=get_available_update()),
daemon=True,
)
if not args.no_banner and not args.only_logs:
update_check_thread.start()
if not args.no_banner:
print_banner(args)
@@ -343,15 +325,6 @@ def prowler():
audit_output_options.output_directory,
)
# Promote Prowler Cloud as the last thing the user sees after the results,
# preceded by an update notice when a newer release is available
if not args.no_banner and not args.only_logs:
if update_check_thread.is_alive():
update_check_thread.join(timeout=2)
if available_update.get("latest"):
print_update_notice(available_update["latest"])
print_prowler_cloud_banner()
# If custom checks were passed, remove the modules
if checks_folder:
remove_custom_checks_module(checks_folder, provider)
+67 -7
View File
@@ -3044,7 +3044,7 @@
"Id": "9.4",
"Description": "Ensure that Register with Entra ID is enabled on App Service",
"Checks": [
""
"app_client_certificates_on"
],
"Attributes": [
{
@@ -3066,7 +3066,7 @@
"Id": "9.5",
"Description": "Ensure That 'PHP version' is the Latest, If Used to Run the Web App",
"Checks": [
"app_ensure_php_version_is_latest"
"app_register_with_identity"
],
"Attributes": [
{
@@ -3088,7 +3088,7 @@
"Id": "9.6",
"Description": "Ensure that 'Python version' is the Latest Stable Version, if Used to Run the Web App",
"Checks": [
"app_ensure_python_version_is_latest"
"app_ensure_php_version_is_latest"
],
"Attributes": [
{
@@ -3110,7 +3110,7 @@
"Id": "9.7",
"Description": "Ensure that 'Java version' is the latest, if used to run the Web App",
"Checks": [
"app_ensure_java_version_is_latest"
"app_ensure_python_version_is_latest"
],
"Attributes": [
{
@@ -3132,7 +3132,7 @@
"Id": "9.8",
"Description": "Ensure that 'HTTP Version' is the Latest, if Used to Run the Web App",
"Checks": [
"app_ensure_using_http20"
"app_ensure_java_version_is_latest"
],
"Attributes": [
{
@@ -3154,7 +3154,7 @@
"Id": "9.9",
"Description": "Ensure FTP deployments are Disabled",
"Checks": [
"app_ftp_deployment_disabled"
"app_ensure_using_http20"
],
"Attributes": [
{
@@ -3176,7 +3176,7 @@
"Id": "9.10",
"Description": "Ensure Azure Key Vaults are Used to Store Secrets",
"Checks": [
""
"app_ftp_deployment_disabled"
],
"Attributes": [
{
@@ -3213,6 +3213,66 @@
"References": "https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-lock-resources:https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-manager-subscription-governance#azure-resource-locks:https://docs.microsoft.com/en-us/azure/governance/blueprints/concepts/resource-locking:https://learn.microsoft.com/en-us/security/benchmark/azure/mcsb-asset-management#am-4-limit-access-to-asset-management"
}
]
},
{
"Id": "9.10",
"Description": "Ensure FTP deployments are Disabled",
"Checks": [],
"Attributes": [
{
"Section": "9. AppService",
"Profile": "Level 1",
"AssessmentStatus": "Automated",
"Description": "By default, Azure Functions, Web, and API Services can be deployed over FTP. If FTP is required for an essential deployment workflow, FTPS should be required for FTP login for all App Service Apps and Functions.",
"RationaleStatement": "Azure FTP deployment endpoints are public. An attacker listening to traffic on a wifi network used by a remote employee or a corporate network could see login traffic in clear-text which would then grant them full control of the code base of the app or service. This finding is more severe if User Credentials for deployment are set at the subscription level rather than using the default Application Credentials which are unique per App.",
"ImpactStatement": "Any deployment workflows that rely on FTP or FTPs rather than the WebDeploy or HTTPs endpoints may be affected.",
"RemediationProcedure": "**From Azure Portal** 1. Go to the Azure Portal 2. Select `App Services` 3. Click on an app 4. Select `Settings` and then `Configuration` 5. Under `General Settings`, for the `Platform Settings`, the `FTP state` should be set to `Disabled` or `FTPS Only` **From Azure CLI** For each out of compliance application, run the following choosing either 'disabled' or 'FtpsOnly' as appropriate: ``` az webapp config set --resource-group <resource group name> --name <app name> --ftps-state [disabled|FtpsOnly] ``` **From PowerShell** For each out of compliance application, run the following: ``` Set-AzWebApp -ResourceGroupName <resource group name> -Name <app name> -FtpsState <Disabled or FtpsOnly> ```",
"AuditProcedure": "**From Azure Portal** 1. Go to the Azure Portal 2. Select `App Services` 3. Click on an app 4. Select `Settings` and then `Configuration` 5. Under `General Settings`, for the `Platform Settings`, the `FTP state` should not be set to `All allowed` **From Azure CLI** List webapps to obtain the ids. ``` az webapp list ``` List the publish profiles to obtain the username, password and ftp server url. ``` az webapp deployment list-publishing-profiles --ids <ids> { publishUrl: <URL_FOR_WEB_APP>, userName: <USER_NAME>, userPWD: <USER_PASSWORD>, } ``` **From PowerShell** List all Web Apps: ``` Get-AzWebApp ``` For each app: ``` Get-AzWebApp -ResourceGroupName <resource group name> -Name <app name> | Select-Object -ExpandProperty SiteConfig ``` In the output, look for the value of **FtpsState**. If its value is **AllAllowed** the setting is out of compliance. Any other value is considered in compliance with this check.",
"AdditionalInformation": "",
"DefaultValue": "[Azure Web Service Deploy via FTP](https://docs.microsoft.com/en-us/azure/app-service/deploy-ftp):[Azure Web Service Deployment](https://docs.microsoft.com/en-us/azure/app-service/overview-security):https://docs.microsoft.com/en-us/security/benchmark/azure/security-controls-v3-data-protection#dp-4-encrypt-sensitive-information-in-transit:https://docs.microsoft.com/en-us/security/benchmark/azure/security-controls-v3-posture-vulnerability-management#pv-7-rapidly-and-automatically-remediate-software-vulnerabilities",
"References": "TA0008, T1570, M1031"
}
]
},
{
"Id": "9.11",
"Description": "Ensure Azure Key Vaults are Used to Store Secrets",
"Checks": [],
"Attributes": [
{
"Section": "9. AppService",
"Profile": "Level 2",
"AssessmentStatus": "Manual",
"Description": "Azure Key Vault will store multiple types of sensitive information such as encryption keys, certificate thumbprints, and Managed Identity Credentials. Access to these 'Secrets' can be controlled through granular permissions.",
"RationaleStatement": "The credentials given to an application have permissions to create, delete, or modify data stored within the systems they access. If these credentials are stored within the application itself, anyone with access to the application or a copy of the code has access to them. Storing within Azure Key Vault as secrets increases security by controlling access. This also allows for updates of the credentials without redeploying the entire application.",
"ImpactStatement": "Integrating references to secrets within the key vault are required to be specifically integrated within the application code. This will require additional configuration to be made during the writing of an application, or refactoring of an already written one. There are also additional costs that are charged per 10000 requests to the Key Vault.",
"RemediationProcedure": "Remediation has 2 steps 1. Setup the Key Vault 2. Setup the App Service to use the Key Vault **Step 1: Set up the Key Vault** **From Azure CLI** ``` az keyvault create --name <name> --resource-group <myResourceGroup> --location myLocation ``` **From Powershell** ``` New-AzKeyvault -name <name> -ResourceGroupName <myResourceGroup> -Location <myLocation> ``` **Step 2: Set up the App Service to use the Key Vault** Sample JSON Template for App Service Configuration: ``` { //... resources: [ { type: Microsoft.Storage/storageAccounts, name: [variables('storageAccountName')], //... }, { type: Microsoft.Insights/components, name: [variables('appInsightsName')], //... }, { type: Microsoft.Web/sites, name: [variables('functionAppName')], identity: { type: SystemAssigned }, //... resources: [ { type: config, name: appsettings, //... dependsOn: [ [resourceId('Microsoft.Web/sites', variables('functionAppName'))], [resourceId('Microsoft.KeyVault/vaults/', variables('keyVaultName'))], [resourceId('Microsoft.KeyVault/vaults/secrets', variables('keyVaultName'), variables('storageConnectionStringName'))], [resourceId('Microsoft.KeyVault/vaults/secrets', variables('keyVaultName'), variables('appInsightsKeyName'))] ], properties: { AzureWebJobsStorage: [concat('@Microsoft.KeyVault(SecretUri=', reference(variables('storageConnectionStringResourceId')).secretUriWithVersion, ')')], WEBSITE_CONTENTAZUREFILECONNECTIONSTRING: [concat('@Microsoft.KeyVault(SecretUri=', reference(variables('storageConnectionStringResourceId')).secretUriWithVersion, ')')], APPINSIGHTS_INSTRUMENTATIONKEY: [concat('@Microsoft.KeyVault(SecretUri=', reference(variables('appInsightsKeyResourceId')).secretUriWithVersion, ')')], WEBSITE_ENABLE_SYNC_UPDATE_SITE: true //... } }, { type: sourcecontrols, name: web, //... dependsOn: [ [resourceId('Microsoft.Web/sites', variables('functionAppName'))], [resourceId('Microsoft.Web/sites/config', variables('functionAppName'), 'appsettings')] ], } ] }, { type: Microsoft.KeyVault/vaults, name: [variables('keyVaultName')], //... dependsOn: [ [resourceId('Microsoft.Web/sites', variables('functionAppName'))] ], properties: { //... accessPolicies: [ { tenantId: [reference(concat('Microsoft.Web/sites/', variables('functionAppName'), '/providers/Microsoft.ManagedIdentity/Identities/default'), '2015-08-31-PREVIEW').tenantId], objectId: [reference(concat('Microsoft.Web/sites/', variables('functionAppName'), '/providers/Microsoft.ManagedIdentity/Identities/default'), '2015-08-31-PREVIEW').principalId], permissions: { secrets: [ get ] } } ] }, resources: [ { type: secrets, name: [variables('storageConnectionStringName')], //... dependsOn: [ [resourceId('Microsoft.KeyVault/vaults/', variables('keyVaultName'))], [resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))] ], properties: { value: [concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';AccountKey=', listKeys(variables('storageAccountResourceId'),'2015-05-01-preview').key1)] } }, { type: secrets, name: [variables('appInsightsKeyName')], //... dependsOn: [ [resourceId('Microsoft.KeyVault/vaults/', variables('keyVaultName'))], [resourceId('Microsoft.Insights/components', variables('appInsightsName'))] ], properties: { value: [reference(resourceId('microsoft.insights/components/', variables('appInsightsName')), '2015-05-01').InstrumentationKey] } } ] } ] } ```",
"AuditProcedure": "**From Azure Portal** 1. Login to Azure Portal 2. In the expandable menu on the left go to `Key Vaults` 3. View the Key Vaults listed. **From Azure CLI** To list key vaults within a subscription run the following command: ``` Get-AzKeyVault ``` To list the secrets within these key vaults run the following command: ``` Get-AzKeyVaultSecret [-VaultName] <vault name> ``` **From Powershell** To list key vaults within a subscription run the following command: ``` Get-AzKeyVault ``` To list all secrets in a key vault run the following command: ``` Get-AzKeyVaultSecret -VaultName '<vaultName' ```",
"AdditionalInformation": "",
"DefaultValue": "https://docs.microsoft.com/en-us/azure/app-service/app-service-key-vault-references:https://docs.microsoft.com/en-us/security/benchmark/azure/security-controls-v3-identity-management#im-2-manage-application-identities-securely-and-automatically:https://docs.microsoft.com/en-us/cli/azure/keyvault?view=azure-cli-latest:https://docs.microsoft.com/en-us/cli/azure/keyvault?view=azure-cli-latest",
"References": "TA0006, T1552, M1041"
}
]
},
{
"Id": "10.1",
"Description": "Ensure that Resource Locks are set for Mission-Critical Azure Resources",
"Checks": [],
"Attributes": [
{
"Section": "10. Miscellaneous",
"Profile": "Level 2",
"AssessmentStatus": "Manual",
"Description": "Resource Manager Locks provide a way for administrators to lock down Azure resources to prevent deletion of, or modifications to, a resource. These locks sit outside of the Role Based Access Controls (RBAC) hierarchy and, when applied, will place restrictions on the resource for all users. These locks are very useful when there is an important resource in a subscription that users should not be able to delete or change. Locks can help prevent accidental and malicious changes or deletion.",
"RationaleStatement": "As an administrator, it may be necessary to lock a subscription, resource group, or resource to prevent other users in the organization from accidentally deleting or modifying critical resources. The lock level can be set to to `CanNotDelete` or `ReadOnly` to achieve this purpose. - `CanNotDelete` means authorized users can still read and modify a resource, but they cannot delete the resource. - `ReadOnly` means authorized users can read a resource, but they cannot delete or update the resource. Applying this lock is similar to restricting all authorized users to the permissions granted by the Reader role.",
"ImpactStatement": "There can be unintended outcomes of locking a resource. Applying a lock to a parent service will cause it to be inherited by all resources within. Conversely, applying a lock to a resource may not apply to connected storage, leaving it unlocked. Please see the documentation for further information.",
"RemediationProcedure": "**From Azure Portal** 1. Navigate to the specific Azure Resource or Resource Group 2. For each mission critical resource, click on `Locks` 3. Click `Add` 4. Give the lock a name and a description, then select the type, `Read-only` or `Delete` as appropriate 5. Click OK **From Azure CLI** To lock a resource, provide the name of the resource, its resource type, and its resource group name. ``` az lock create --name <LockName> --lock-type <CanNotDelete/Read-only> --resource-group <resourceGroupName> --resource-name <resourceName> --resource-type <resourceType> ``` **From Powershell** ``` Get-AzResourceLock -ResourceName <Resource Name> -ResourceType <Resource Type> -ResourceGroupName <Resource Group Name> -Locktype <CanNotDelete/Read-only> ```",
"AuditProcedure": "**From Azure Portal** 1. Navigate to the specific Azure Resource or Resource Group 2. Click on `Locks` 3. Ensure the lock is defined with name and description, with type `Read-only` or `Delete` as appropriate. **From Azure CLI** Review the list of all locks set currently: ``` az lock list --resource-group <resourcegroupname> --resource-name <resourcename> --namespace <Namespace> --resource-type <type> --parent ``` **From Powershell** Run the following command to list all resources. ``` Get-AzResource ``` For each resource, run the following command to check for Resource Locks. ``` Get-AzResourceLock -ResourceName <Resource Name> -ResourceType <Resource Type> -ResourceGroupName <Resource Group Name> ``` Review the output of the `Properties` setting. Compliant settings will have the `CanNotDelete` or `ReadOnly` value.",
"AdditionalInformation": "",
"DefaultValue": "https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-lock-resources:https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-manager-subscription-governance#azure-resource-locks:https://docs.microsoft.com/en-us/azure/governance/blueprints/concepts/resource-locking:https://docs.microsoft.com/en-us/security/benchmark/azure/security-controls-v3-asset-management#am-4-limit-access-to-asset-management",
"References": ""
}
]
}
]
}
+19 -46
View File
@@ -11,7 +11,7 @@ from prowler.lib.logger import logger
timestamp = datetime.today()
timestamp_utc = datetime.now(timezone.utc).replace(tzinfo=timezone.utc)
prowler_version = "3.16.18"
prowler_version = "3.16.11"
html_logo_url = "https://github.com/prowler-cloud/prowler/"
html_logo_img = "https://user-images.githubusercontent.com/3985464/113734260-7ba06900-96fb-11eb-82bc-d4f68a1e2710.png"
square_logo_img = "https://user-images.githubusercontent.com/38561120/235905862-9ece5bd7-9aa3-4e48-807a-3a9035eb8bfb.png"
@@ -61,53 +61,25 @@ html_file_suffix = ".html"
default_config_file_path = (
f"{pathlib.Path(os.path.dirname(os.path.realpath(__file__)))}/config.yaml"
)
encoding_format_utf_8 = "utf-8"
def get_latest_release_version():
"""Return the latest Prowler release tag name from GitHub, or None if it cannot be retrieved."""
try:
release_response = requests.get(
"https://api.github.com/repos/prowler-cloud/prowler/tags", timeout=1
)
return release_response.json()[0]["name"]
except Exception:
return None
def _version_tuple(version_string):
"""Return a comparable tuple from a dotted version string."""
return tuple(int(part) for part in version_string.split("."))
def get_available_update():
"""Return the latest Prowler version if it is newer than the running one, None otherwise.
Honors the PROWLER_NO_VERSION_CHECK and DO_NOT_TRACK environment variables:
when either is set, no network call is made and None is returned.
"""
if os.environ.get("PROWLER_NO_VERSION_CHECK") or os.environ.get("DO_NOT_TRACK"):
return None
latest_version = get_latest_release_version()
try:
if latest_version and _version_tuple(latest_version) > _version_tuple(
prowler_version
):
return latest_version
except Exception:
return None
return None
def check_current_version():
prowler_version_string = f"Prowler {prowler_version}"
latest_version = get_latest_release_version()
if not latest_version:
return prowler_version_string
if latest_version != prowler_version:
return f"{prowler_version_string} (latest is {latest_version}, upgrade for the latest features)"
else:
return f"{prowler_version_string} (You are running the latest version, yay!)"
try:
prowler_version_string = f"Prowler {prowler_version}"
release_response = requests.get(
"https://api.github.com/repos/prowler-cloud/prowler/tags", timeout=1
)
latest_version = release_response.json()[0]["name"]
if latest_version != prowler_version:
return f"{prowler_version_string} (latest is {latest_version}, upgrade for the latest features)"
else:
return (
f"{prowler_version_string} (You are running the latest version, yay!)"
)
except requests.RequestException:
return f"{prowler_version_string}"
except Exception:
return f"{prowler_version_string}"
def change_config_var(variable: str, value: str, audit_info):
@@ -130,7 +102,8 @@ def load_and_validate_config_file(provider: str, config_file_path: str) -> dict:
load_and_validate_config_file reads the Prowler config file in YAML format from the default location or the file passed with the --config-file flag
"""
try:
with open(config_file_path, "r", encoding=encoding_format_utf_8) as f:
with open(config_file_path) as f:
config = {}
config_file = yaml.safe_load(f)
# Not to introduce a breaking change we have to allow the old format config file without any provider keys
-1
View File
@@ -31,7 +31,6 @@ aws:
max_ec2_instance_age_in_days: 180
# AWS VPC Configuration (vpc_endpoint_connections_trust_boundaries, vpc_endpoint_services_allowed_principals_trust_boundaries)
# AWS SSM Configuration (aws.ssm_documents_set_as_public)
# Single account environment: No action required. The AWS account number will be automatically added by the checks.
# Multi account environment: Any additional trusted account number should be added as a space separated list, e.g.
# trusted_account_ids : ["123456789012", "098765432109", "678901234567"]
-69
View File
@@ -2,75 +2,6 @@ from colorama import Fore, Style
from prowler.config.config import banner_color, orange_color, prowler_version, timestamp
# Prowler Cloud landing URL used by the CLI banner. The visible text stays
# "cloud.prowler.com" while the clickable target carries the UTM parameters so
# terminals that support OSC 8 hyperlinks attribute the visit to the v3 CLI.
CLOUD_DISPLAY_TEXT = "cloud.prowler.com"
CLOUD_BANNER_URL = (
"https://cloud.prowler.com/sign-up?utm_source=prowler-cli&utm_content=v3"
)
def _hyperlink(url, text):
"""Wrap ``text`` in an OSC 8 terminal hyperlink pointing to ``url``.
Terminals that support OSC 8 render ``text`` as a clickable link to ``url``;
those that do not simply display ``text`` unchanged.
"""
return f"\033]8;;{url}\033\\{text}\033]8;;\033\\"
def print_update_notice(latest_version):
"""
Prints a notice that a newer Prowler version is available.
Parameters:
- latest_version (str): The latest released Prowler version.
Returns:
- None
"""
print(
f"\n{Fore.YELLOW}A new version of Prowler is available: {prowler_version}{latest_version}{Style.RESET_ALL}\n"
f"Upgrading from Prowler v3 is a major version upgrade — see the release notes at\n"
f"https://github.com/prowler-cloud/prowler/releases before upgrading.\n"
f"Upgrade with: {Style.BRIGHT}pipx upgrade prowler{Style.RESET_ALL} "
f"(disable this check with PROWLER_NO_VERSION_CHECK=1)"
)
def print_prowler_cloud_banner():
"""
Prints a promotional banner highlighting what Prowler Cloud adds on top of
the open-source CLI.
Shown at the end of a scan to let users know about the managed platform
capabilities they are missing.
Returns:
- None
"""
check = f"{Fore.GREEN}{Style.RESET_ALL}"
bar = f"{banner_color}{Style.RESET_ALL}"
print(
f"""
{bar} {Style.BRIGHT}You're getting a snapshot 📸. Prowler Cloud gives you the full picture:{Style.RESET_ALL}
{bar}
{bar} {check} {Style.BRIGHT}Send your findings{Style.RESET_ALL} - directly from the Prowler CLI to Prowler Cloud.
{bar} {check} {Style.BRIGHT}Continuous Security Monitoring{Style.RESET_ALL} - custom scheduling and scan configuration with history, trends and alerts.
{bar} {check} {Style.BRIGHT}Triage{Style.RESET_ALL} - review findings, flag false positives and track accepted risk with your team.
{bar} {check} {Style.BRIGHT}Lighthouse AI + MCP{Style.RESET_ALL} - autonomous triage, custom dashboards, prioritization with prevention and remediation.
{bar} {check} {Style.BRIGHT}Alerts{Style.RESET_ALL} - get notified when anything you want is happening.
{bar} {check} {Style.BRIGHT}Live Compliance{Style.RESET_ALL} - dashboards for 50+ frameworks, always up to date.
{bar} {check} {Style.BRIGHT}Remediation{Style.RESET_ALL} - complete guided remediation including Autonomous remediation with Lighthouse AI.
{bar} {check} {Style.BRIGHT}Attack Path Visualization{Style.RESET_ALL} - see how attackers chain risks to reach your crown jewels.
{bar} {check} {Style.BRIGHT}Bulk Provisioning{Style.RESET_ALL} - add your entire AWS Organization in seconds.
{bar} {check} {Style.BRIGHT}Integrations{Style.RESET_ALL} - Anything with our MCP + Jira, Slack, AWS Security Hub, Amazon S3, SSO and RBAC.
{bar}
{bar} {banner_color}Start free at 👉 {_hyperlink(CLOUD_BANNER_URL, CLOUD_DISPLAY_TEXT)}{Style.RESET_ALL}
"""
)
def print_banner(args):
banner = rf"""{banner_color} _
+2 -3
View File
@@ -12,14 +12,13 @@ from time import mktime
from detect_secrets import SecretsCollection
from detect_secrets.settings import default_settings
from prowler.config.config import encoding_format_utf_8
from prowler.lib.logger import logger
def open_file(input_file: str, mode: str = "r") -> TextIOWrapper:
"""open_file returns a handler to the file using the specified mode."""
try:
f = open(input_file, mode, encoding=encoding_format_utf_8)
f = open(input_file, mode)
except OSError as os_error:
if os_error.strerror == "Too many open files":
logger.critical(
@@ -67,7 +66,7 @@ def file_exists(filename: str):
def hash_sha512(string: str) -> str:
"""hash_sha512 returns the first 9 bytes of the SHA512 representation for the given string."""
return sha512(string.encode(encoding_format_utf_8)).hexdigest()[0:9]
return sha512(string.encode("utf-8")).hexdigest()[0:9]
def detect_secrets_scan(data):
+2 -1
View File
@@ -125,7 +125,8 @@ class AWS_Provider:
token=response["Credentials"]["SessionToken"],
expiry_time=response["Credentials"]["Expiration"].isoformat(),
)
logger.info("Refreshed Credentials")
logger.info("Refreshed Credentials:")
logger.info(refreshed_credentials)
return refreshed_credentials
File diff suppressed because it is too large Load Diff
-2
View File
@@ -45,8 +45,6 @@ def parse_iam_credentials_arn(arn: str) -> ARN:
arn_parsed.resource_type != "role"
and arn_parsed.resource_type != "user"
and arn_parsed.resource_type != "assumed-role"
and arn_parsed.resource_type != "root"
and arn_parsed.resource_type != "federated-user"
):
raise RoleArnParsingInvalidResourceType
elif arn_parsed.resource == "":
@@ -1,7 +1,5 @@
def is_condition_block_restrictive(
condition_statement: dict,
source_account: str,
is_cross_account_allowed=False,
condition_statement: dict, source_account: str, is_cross_account_allowed=False
):
"""
is_condition_block_restrictive parses the IAM Condition policy block and, by default, returns True if the source_account passed as argument is within, False if not.
@@ -17,9 +15,6 @@ def is_condition_block_restrictive(
}
@param source_account: str with a 12-digit AWS Account number, e.g.: 111122223333
@param is_cross_account_allowed: bool to allow cross-account access, e.g.: True
"""
is_condition_valid = False
@@ -95,63 +90,3 @@ def is_condition_block_restrictive(
is_condition_valid = True
return is_condition_valid
def is_condition_block_restrictive_organization(
condition_statement: dict,
):
"""
is_condition_block_restrictive_organization parses the IAM Condition policy block and returns True if the condition_statement is restrictive for the organization, False if not.
@param condition_statement: dict with an IAM Condition block, e.g.:
{
"StringLike": {
"AWS:PrincipalOrgID": "o-111122223333"
}
}
"""
is_condition_valid = False
# The conditions must be defined in lowercase since the context key names are not case-sensitive.
# For example, including the aws:PrincipalOrgID context key is equivalent to testing for AWS:PrincipalOrgID
# https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_condition.html
valid_condition_options = {
"StringEquals": [
"aws:principalorgid",
],
"StringLike": [
"aws:principalorgid",
],
}
for condition_operator, condition_operator_key in valid_condition_options.items():
if condition_operator in condition_statement:
for value in condition_operator_key:
# We need to transform the condition_statement into lowercase
condition_statement[condition_operator] = {
k.lower(): v
for k, v in condition_statement[condition_operator].items()
}
if value in condition_statement[condition_operator]:
# values are a list
if isinstance(
condition_statement[condition_operator][value],
list,
):
is_condition_valid = True
for item in condition_statement[condition_operator][value]:
if item == "*":
is_condition_valid = False
break
# value is a string
elif isinstance(
condition_statement[condition_operator][value],
str,
):
if "*" not in condition_statement[condition_operator][value]:
is_condition_valid = True
return is_condition_valid
@@ -12,7 +12,7 @@
"SubServiceName": "rest_api",
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
"Severity": "medium",
"ResourceType": "AwsApiGatewayRestApi",
"ResourceType": "AwsApiGatewayStage",
"Description": "Check if API Gateway Stage has client certificate enabled to access your backend endpoint.",
"Risk": "Possible man in the middle attacks and other similar risks.",
"RelatedUrl": "",
@@ -12,7 +12,7 @@
"SubServiceName": "rest_api",
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
"Severity": "medium",
"ResourceType": "AwsApiGatewayRestApi",
"ResourceType": "AwsApiGatewayStage",
"Description": "Check if API Gateway Stage has logging enabled.",
"Risk": "If not enabled, monitoring of service use is not possible. Real-time monitoring of API calls can be achieved by directing CloudTrail Logs to CloudWatch Logs and establishing corresponding metric filters and alarms.",
"RelatedUrl": "",
@@ -12,7 +12,7 @@
"SubServiceName": "rest_api",
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
"Severity": "medium",
"ResourceType": "AwsApiGatewayRestApi",
"ResourceType": "AwsApiGatewayStage",
"Description": "Check if API Gateway Stage has a WAF ACL attached.",
"Risk": "Potential attacks and / or abuse of service, more even for even for internet reachable services.",
"RelatedUrl": "",
@@ -11,7 +11,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:appstream:region:account-id:fleet/resource-id",
"Severity": "medium",
"ResourceType": "Other",
"ResourceType": "AppStream",
"Description": "Ensure default Internet Access from your Amazon AppStream fleet streaming instances should remain unchecked.",
"Risk": "Default Internet Access from your fleet streaming instances should be controlled using a NAT gateway in the VPC.",
"RelatedUrl": "https://docs.aws.amazon.com/appstream2/latest/developerguide/set-up-stacks-fleets.html",
@@ -9,7 +9,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:appstream:region:account-id:fleet/resource-id",
"Severity": "medium",
"ResourceType": "Other",
"ResourceType": "AppStream",
"Description": "Ensure user maximum session duration is no longer than 10 hours.",
"Risk": "Having a session duration lasting longer than 10 hours should not be necessary and if running for any malicious reasons provides a greater time for usage than should be allowed.",
"RelatedUrl": "https://docs.aws.amazon.com/appstream2/latest/developerguide/set-up-stacks-fleets.html",
@@ -11,7 +11,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:appstream:region:account-id:fleet/resource-id",
"Severity": "medium",
"ResourceType": "Other",
"ResourceType": "AppStream",
"Description": "Ensure session disconnect timeout is set to 5 minutes or less",
"Risk": "Disconnect timeout in minutes, is the amount of of time that a streaming session remains active after users disconnect.",
"RelatedUrl": "https://docs.aws.amazon.com/appstream2/latest/developerguide/set-up-stacks-fleets.html",
@@ -11,7 +11,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:appstream:region:account-id:fleet/resource-id",
"Severity": "medium",
"ResourceType": "Other",
"ResourceType": "AppStream",
"Description": "Ensure session idle disconnect timeout is set to 10 minutes or less.",
"Risk": "Idle disconnect timeout in minutes is the amount of time that users can be inactive before they are disconnected from their streaming session and the Disconnect timeout in minutes time begins.",
"RelatedUrl": "https://docs.aws.amazon.com/appstream2/latest/developerguide/set-up-stacks-fleets.html",
@@ -9,7 +9,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:athena:region:account-id:workgroup/resource-id",
"Severity": "medium",
"ResourceType": "AwsAthenaWorkGroup",
"ResourceType": "WorkGroup",
"Description": "Ensure that encryption at rest is enabled for Amazon Athena query results stored in Amazon S3 in order to secure data and meet compliance requirements for data-at-rest encryption.",
"Risk": "If not enabled sensitive information at rest is not protected.",
"RelatedUrl": "https://docs.aws.amazon.com/athena/latest/ug/encryption.html",
@@ -9,7 +9,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:athena:region:account-id:workgroup/resource-id",
"Severity": "medium",
"ResourceType": "AwsAthenaWorkGroup",
"ResourceType": "WorkGroup",
"Description": "Ensure that workgroup configuration is enforced so it cannot be overriden by client-side settings.",
"Risk": "If workgroup configuration is not enforced security settings like encryption can be overriden by client-side settings.",
"RelatedUrl": "https://docs.aws.amazon.com/athena/latest/ug/workgroups-settings-override.html",
@@ -9,7 +9,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:autoscaling:region:account-id:autoScalingGroupName/resource-name",
"Severity": "critical",
"ResourceType": "AwsAutoScalingLaunchConfiguration",
"ResourceType": "Other",
"Description": "Find secrets in EC2 Auto Scaling Launch Configuration",
"Risk": "The use of a hard-coded password increases the possibility of password guessing. If hard-coded passwords are used, it is possible that malicious users gain access through the account in question.",
"RelatedUrl": "",
@@ -6,9 +6,7 @@ from base64 import b64decode
from detect_secrets import SecretsCollection
from detect_secrets.settings import default_settings
from prowler.config.config import encoding_format_utf_8
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.lib.logger import logger
from prowler.providers.aws.services.autoscaling.autoscaling_client import (
autoscaling_client,
)
@@ -27,23 +25,12 @@ class autoscaling_find_secrets_ec2_launch_configuration(Check):
temp_user_data_file = tempfile.NamedTemporaryFile(delete=False)
user_data = b64decode(configuration.user_data)
try:
if user_data[0:2] == b"\x1f\x8b": # GZIP magic number
user_data = zlib.decompress(
user_data, zlib.MAX_WBITS | 32
).decode(encoding_format_utf_8)
else:
user_data = user_data.decode(encoding_format_utf_8)
except UnicodeDecodeError as error:
logger.warning(
f"{configuration.region} -- Unable to decode user data in autoscaling launch configuration {configuration.name}: {error}"
if user_data[0:2] == b"\x1f\x8b": # GZIP magic number
user_data = zlib.decompress(user_data, zlib.MAX_WBITS | 32).decode(
"utf-8"
)
continue
except Exception as error:
logger.warning(
f"{configuration.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
continue
else:
user_data = user_data.decode("utf-8")
temp_user_data_file.write(
bytes(user_data, encoding="raw_unicode_escape")
@@ -7,7 +7,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:autoscaling:region:account-id:autoScalingGroupName/resource-name",
"Severity": "medium",
"ResourceType": "AwsAutoScalingAutoScalingGroup",
"ResourceType": "Other",
"Description": "EC2 Auto Scaling Group should use multiple Availability Zones",
"Risk": "In case of a failure in a single Availability Zone, the Auto Scaling Group will not be able to launch new instances to replace the failed ones.",
"RelatedUrl": "https://docs.aws.amazon.com/autoscaling/ec2/userguide/as-add-availability-zone.html",
@@ -28,5 +28,5 @@
],
"DependsOn": [],
"RelatedTo": [],
"Notes": "It gives a false positive if the function is exposed publicly by an other public resource like an ALB or API Gateway in an AWS Account when an AWS account ID is set as the principal of the policy."
"Notes": ""
}
@@ -19,30 +19,20 @@ class awslambda_function_not_publicly_accessible(Check):
if function.policy:
for statement in function.policy["Statement"]:
# Only check allow statements
if statement["Effect"] == "Allow" and (
"*" in statement["Principal"]
or (
isinstance(statement["Principal"], dict)
and (
"*" in statement["Principal"].get("AWS", "")
or "*"
in statement["Principal"].get("CanonicalUser", "")
or ( # Check if function can be invoked by other AWS services
(
".amazonaws.com"
in statement["Principal"].get("Service", "")
)
and (
"*" in statement.get("Action", "")
or "InvokeFunction"
in statement.get("Action", "")
)
)
if statement["Effect"] == "Allow":
if (
"*" in statement["Principal"]
or (
"AWS" in statement["Principal"]
and "*" in statement["Principal"]["AWS"]
)
)
):
public_access = True
break
or (
"CanonicalUser" in statement["Principal"]
and "*" in statement["Principal"]["CanonicalUser"]
)
):
public_access = True
break
if public_access:
report.status = "FAIL"
@@ -14,6 +14,7 @@ from prowler.lib.scan_filters.scan_filters import is_resource_filtered
from prowler.providers.aws.lib.service.service import AWSService
################## Lambda
class Lambda(AWSService):
def __init__(self, audit_info):
# Call AWSService's __init__
@@ -11,7 +11,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:service:region:account-id:backup-report-plan:backup-report-plan-id",
"Severity": "low",
"ResourceType": "AwsBackupBackupPlan",
"ResourceType": "Other",
"Description": "This check ensures that there is at least one backup report plan in place.",
"Risk": "Without a backup report plan, an organization may lack visibility into the success or failure of backup operations.",
"RelatedUrl": "https://docs.aws.amazon.com/aws-backup/latest/devguide/create-report-plan-console.html",
@@ -29,12 +29,7 @@ class cloudformation_stack_outputs_find_secrets(Check):
# Store the CloudFormation Stack Outputs into a file
for output in stack.outputs:
temp_output_file.write(
bytes(
f"{output}\n",
encoding="raw_unicode_escape",
)
)
temp_output_file.write(f"{output}".encode())
temp_output_file.close()
# Init detect_secrets
@@ -43,17 +38,11 @@ class cloudformation_stack_outputs_find_secrets(Check):
with default_settings():
secrets.scan_file(temp_output_file.name)
detect_secrets_output = secrets.json()
# If secrets are found, update the report status
if detect_secrets_output:
secrets_string = ", ".join(
[
f"{secret['type']} in Output {int(secret['line_number'])}"
for secret in detect_secrets_output[temp_output_file.name]
]
)
if secrets.json():
report.status = "FAIL"
report.status_extended = f"Potential secret found in Stack {stack.name} Outputs -> {secrets_string}."
report.status_extended = (
f"Potential secret found in Stack {stack.name} Outputs."
)
os.remove(temp_output_file.name)
else:
@@ -9,7 +9,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
"Severity": "low",
"ResourceType": "AwsCloudTrailTrail",
"ResourceType": "AwsS3Bucket",
"Description": "Ensure that all your AWS CloudTrail trails are configured to log Data events in order to record S3 object-level API operations, such as GetObject, DeleteObject and PutObject, for individual S3 buckets or for all current and future S3 buckets provisioned in your AWS account.",
"Risk": "If logs are not enabled, monitoring of service use and threat analysis is not possible.",
"RelatedUrl": "",
@@ -9,7 +9,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
"Severity": "low",
"ResourceType": "AwsCloudTrailTrail",
"ResourceType": "AwsS3Bucket",
"Description": "Ensure that all your AWS CloudTrail trails are configured to log Data events in order to record S3 object-level API operations, such as GetObject, DeleteObject and PutObject, for individual S3 buckets or for all current and future S3 buckets provisioned in your AWS account.",
"Risk": "If logs are not enabled, monitoring of service use and threat analysis is not possible.",
"RelatedUrl": "",
@@ -9,7 +9,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:cloudwatch:region:account-id:certificate/resource-id",
"Severity": "medium",
"ResourceType": "AwsCloudWatchAlarm",
"ResourceType": "AwsCloudTrailTrail",
"Description": "Ensure a log metric filter and alarm exist for changes to Network Access Control Lists (NACL).",
"Risk": "Monitoring unauthorized API calls will help reveal application errors and may reduce time to detect malicious activity.",
"RelatedUrl": "https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudwatch-alarms-for-cloudtrail.html",
@@ -9,7 +9,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:cloudwatch:region:account-id:certificate/resource-id",
"Severity": "medium",
"ResourceType": "AwsCloudWatchAlarm",
"ResourceType": "AwsCloudTrailTrail",
"Description": "Ensure a log metric filter and alarm exist for changes to network gateways.",
"Risk": "Monitoring unauthorized API calls will help reveal application errors and may reduce time to detect malicious activity.",
"RelatedUrl": "https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudwatch-alarms-for-cloudtrail.html",
@@ -9,7 +9,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:cloudwatch:region:account-id:certificate/resource-id",
"Severity": "medium",
"ResourceType": "AwsCloudWatchAlarm",
"ResourceType": "AwsCloudTrailTrail",
"Description": "Real-time monitoring of API calls can be achieved by directing Cloud Trail Logs to CloudWatch Logs, or an external Security information and event management (SIEM)environment, and establishing corresponding metric filters and alarms. Routing tablesare used to route network traffic between subnets and to network gateways. It isrecommended that a metric filter and alarm be established for changes to route tables.",
"Risk": "CloudWatch is an AWS native service that allows you to ob serve and monitor resources and applications. CloudTrail Logs can also be sent to an external Security informationand event management (SIEM) environment for monitoring and alerting.Monitoring changes to route tables will help ensure that all VPC traffic flows through anexpected path and prevent any accidental or intentional modifications that may lead touncontrolled network traffic. An alarm should be triggered every time an AWS API call isperformed to create, replace, delete, or disassociate a Route Table.",
"RelatedUrl": "https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudwatch-alarms-for-cloudtrail.html",
@@ -9,7 +9,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:cloudwatch:region:account-id:certificate/resource-id",
"Severity": "medium",
"ResourceType": "AwsCloudWatchAlarm",
"ResourceType": "AwsCloudTrailTrail",
"Description": "Ensure a log metric filter and alarm exist for VPC changes.",
"Risk": "Monitoring unauthorized API calls will help reveal application errors and may reduce time to detect malicious activity.",
"RelatedUrl": "https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudwatch-alarms-for-cloudtrail.html",
@@ -9,7 +9,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:cloudwatch:region:account-id:certificate/resource-id",
"Severity": "medium",
"ResourceType": "AwsAccount",
"ResourceType": "AwsCloudWatch",
"Description": "Check if CloudWatch has allowed cross-account sharing.",
"Risk": "Cross-Account access to CloudWatch could increase the risk of compromising information between accounts.",
"RelatedUrl": "https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Cross-Account-Cross-Region.html",
@@ -9,7 +9,7 @@
"SubServiceName": "logs",
"ResourceIdTemplate": "arn:partition:cloudwatch:region:account-id:certificate/resource-id",
"Severity": "medium",
"ResourceType": "Other",
"ResourceType": "AwsLogsLogGroup",
"Description": "Check if CloudWatch log groups are protected by AWS KMS.",
"Risk": "Using customer managed KMS to encrypt CloudWatch log group provide additional confidentiality and control over the log data.",
"RelatedUrl": "https://docs.aws.amazon.com/cli/latest/reference/logs/associate-kms-key.html",
@@ -10,7 +10,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:cloudwatch:region:account-id:log-group/resource-id",
"Severity": "medium",
"ResourceType": "Other",
"ResourceType": "AwsCloudTrailLogGroup",
"Description": "Check if secrets exists in CloudWatch logs",
"Risk": "Storing sensitive data in CloudWatch logs could allow an attacker with read-only access to escalate their privileges or gain unauthorised access to systems.",
"RelatedUrl": "https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudwatch-alarms-for-cloudtrail.html",
@@ -9,7 +9,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:cloudwatch:region:account-id:certificate/resource-id",
"Severity": "medium",
"ResourceType": "AwsCloudWatchAlarm",
"ResourceType": "AwsCloudTrailTrail",
"Description": "Ensure a log metric filter and alarm exist for AWS Config configuration changes.",
"Risk": "Monitoring unauthorized API calls will help reveal application errors and may reduce time to detect malicious activity.",
"RelatedUrl": "https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudwatch-alarms-for-cloudtrail.html",
@@ -9,7 +9,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:cloudwatch:region:account-id:certificate/resource-id",
"Severity": "medium",
"ResourceType": "AwsCloudWatchAlarm",
"ResourceType": "AwsCloudTrailTrail",
"Description": "Ensure a log metric filter and alarm exist for CloudTrail configuration changes.",
"Risk": "Monitoring unauthorized API calls will help reveal application errors and may reduce time to detect malicious activity.",
"RelatedUrl": "https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudwatch-alarms-for-cloudtrail.html",
@@ -9,7 +9,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:cloudwatch:region:account-id:certificate/resource-id",
"Severity": "medium",
"ResourceType": "AwsCloudWatchAlarm",
"ResourceType": "AwsCloudTrailTrail",
"Description": "Ensure a log metric filter and alarm exist for AWS Management Console authentication failures.",
"Risk": "Monitoring unauthorized API calls will help reveal application errors and may reduce time to detect malicious activity.",
"RelatedUrl": "https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudwatch-alarms-for-cloudtrail.html",
@@ -9,7 +9,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:cloudwatch:region:account-id:certificate/resource-id",
"Severity": "medium",
"ResourceType": "AwsCloudWatchAlarm",
"ResourceType": "AwsCloudTrailTrail",
"Description": "Ensure a log metric filter and alarm exist for AWS Organizations changes.",
"Risk": "Monitoring unauthorized API calls will help reveal application errors and may reduce time to detect malicious activity.",
"RelatedUrl": "https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudwatch-alarms-for-cloudtrail.html",
@@ -9,7 +9,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:cloudwatch:region:account-id:certificate/resource-id",
"Severity": "medium",
"ResourceType": "AwsCloudWatchAlarm",
"ResourceType": "AwsCloudTrailTrail",
"Description": "Ensure a log metric filter and alarm exist for disabling or scheduled deletion of customer created KMS CMKs.",
"Risk": "Monitoring unauthorized API calls will help reveal application errors and may reduce time to detect malicious activity.",
"RelatedUrl": "https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudwatch-alarms-for-cloudtrail.html",
@@ -9,7 +9,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:cloudwatch:region:account-id:certificate/resource-id",
"Severity": "medium",
"ResourceType": "AwsCloudWatchAlarm",
"ResourceType": "AwsCloudTrailTrail",
"Description": "Ensure a log metric filter and alarm exist for S3 bucket policy changes.",
"Risk": "Monitoring unauthorized API calls will help reveal application errors and may reduce time to detect malicious activity.",
"RelatedUrl": "https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudwatch-alarms-for-cloudtrail.html",
@@ -9,7 +9,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:cloudwatch:region:account-id:certificate/resource-id",
"Severity": "medium",
"ResourceType": "AwsCloudWatchAlarm",
"ResourceType": "AwsCloudTrailTrail",
"Description": "Ensure a log metric filter and alarm exist for IAM policy changes.",
"Risk": "Monitoring unauthorized API calls will help reveal application errors and may reduce time to detect malicious activity.",
"RelatedUrl": "https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudwatch-alarms-for-cloudtrail.html",
@@ -9,7 +9,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:cloudwatch:region:account-id:certificate/resource-id",
"Severity": "medium",
"ResourceType": "AwsCloudWatchAlarm",
"ResourceType": "AwsCloudTrailTrail",
"Description": "Ensure a log metric filter and alarm exist for usage of root account.",
"Risk": "Monitoring unauthorized API calls will help reveal application errors and may reduce time to detect malicious activity.",
"RelatedUrl": "https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudwatch-alarms-for-cloudtrail.html",
@@ -9,7 +9,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:cloudwatch:region:account-id:certificate/resource-id",
"Severity": "medium",
"ResourceType": "AwsCloudWatchAlarm",
"ResourceType": "AwsCloudTrailTrail",
"Description": "Ensure a log metric filter and alarm exist for security group changes.",
"Risk": "Monitoring unauthorized API calls will help reveal application errors and may reduce time to detect malicious activity.",
"RelatedUrl": "https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudwatch-alarms-for-cloudtrail.html",
@@ -9,7 +9,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:cloudwatch:region:account-id:certificate/resource-id",
"Severity": "medium",
"ResourceType": "AwsCloudWatchAlarm",
"ResourceType": "AwsCloudTrailTrail",
"Description": "Ensure a log metric filter and alarm exist for Management Console sign-in without MFA.",
"Risk": "Monitoring unauthorized API calls will help reveal application errors and may reduce time to detect malicious activity.",
"RelatedUrl": "https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudwatch-alarms-for-cloudtrail.html",
@@ -9,7 +9,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:cloudwatch:region:account-id:certificate/resource-id",
"Severity": "medium",
"ResourceType": "AwsCloudWatchAlarm",
"ResourceType": "AwsCloudTrailTrail",
"Description": "Ensure a log metric filter and alarm exist for unauthorized API calls.",
"Risk": "Monitoring unauthorized API calls will help reveal application errors and may reduce time to detect malicious activity.",
"RelatedUrl": "https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudwatch-alarms-for-cloudtrail.html",
@@ -7,7 +7,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:codeartifact:region:account-id:directory/directory-id",
"Severity": "medium",
"ResourceType": "Other",
"ResourceType": "AwsDirectoryService",
"Description": "Directory Service monitoring with CloudWatch logs.",
"Risk": "As a best practice, monitor your organization to ensure that changes are logged. This helps you to ensure that any unexpected change can be investigated and unwanted changes can be rolled back.",
"RelatedUrl": "https://docs.aws.amazon.com/directoryservice/latest/admin-guide/incident-response.html",
@@ -7,7 +7,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:codeartifact:region:account-id:directory/directory-id",
"Severity": "medium",
"ResourceType": "Other",
"ResourceType": "AwsDirectoryService",
"Description": "Directory Service has SNS Notifications enabled.",
"Risk": "As a best practice, monitor status of Directory Service. This helps to avoid late actions to fix Directory Service issues.",
"RelatedUrl": "https://docs.aws.amazon.com/directoryservice/latest/admin-guide/ms_ad_enable_notifications.html",
@@ -7,7 +7,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:codeartifact:region:account-id:directory/directory-id",
"Severity": "low",
"ResourceType": "Other",
"ResourceType": "AwsDirectoryService",
"Description": "Directory Service Manual Snapshots limit reached.",
"Risk": "A limit reached can bring unwanted results. The maximum number of manual snapshots is a hard limit.",
"RelatedUrl": "https://docs.aws.amazon.com/general/latest/gr/ds_region.html",
@@ -7,7 +7,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:codeartifact:region:account-id:directory/directory-id",
"Severity": "medium",
"ResourceType": "Other",
"ResourceType": "AwsDirectoryService",
"Description": "Directory Service Manual Snapshots limit reached.",
"Risk": "Expired certificates can impact service availability.",
"RelatedUrl": "https://docs.aws.amazon.com/directoryservice/latest/admin-guide/ms_ad_ldap.html",
@@ -7,7 +7,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:codeartifact:region:account-id:directory/directory-id",
"Severity": "medium",
"ResourceType": "Other",
"ResourceType": "AwsDirectoryService",
"Description": "Ensure Radius server in DS is using the recommended security protocol.",
"Risk": "As a best practice, you might need to configure the authentication protocol between the Microsoft AD DCs and the RADIUS/MFA server. Supported protocols are PAP, CHAP MS-CHAPv1, and MS-CHAPv2. MS-CHAPv2 is recommended because it provides the strongest security of the three options.",
"RelatedUrl": "https://aws.amazon.com/blogs/security/how-to-enable-multi-factor-authentication-for-amazon-workspaces-and-amazon-quicksight-by-using-microsoft-ad-and-on-premises-credentials/",
@@ -7,7 +7,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:codeartifact:region:account-id:directory/directory-id",
"Severity": "medium",
"ResourceType": "Other",
"ResourceType": "AwsDirectoryService",
"Description": "Ensure Multi-Factor Authentication (MFA) using Radius Server is enabled in DS.",
"Risk": "Multi-Factor Authentication (MFA) adds an extra layer of authentication assurance beyond traditional username and password.",
"RelatedUrl": "https://docs.aws.amazon.com/directoryservice/latest/admin-guide/ms_ad_mfa.html",
@@ -9,7 +9,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:dynamodb:region:account-id:certificate/resource-id",
"Severity": "medium",
"ResourceType": "Other",
"ResourceType": "AwsDaxCluster",
"Description": "Check if DynamoDB DAX Clusters are encrypted at rest.",
"Risk": "Encryption at rest provides an additional layer of data protection by securing your data from unauthorized access to the underlying storage.",
"RelatedUrl": "https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DAXEncryptionAtRest.html",
@@ -9,7 +9,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:dynamodb:region:account-id:table/resource-id",
"Severity": "medium",
"ResourceType": "AwsDynamoDbTable",
"ResourceType": "AwsDynamoDBTable",
"Description": "Check if DynamoDB table has encryption at rest enabled using CMK KMS.",
"Risk": "All user data stored in Amazon DynamoDB is fully encrypted at rest. This functionality helps reduce the operational burden and complexity involved in protecting sensitive data.",
"RelatedUrl": "https://docs.aws.amazon.com/amazondynamodbdb/latest/developerguide/EncryptionAtRest.html",
@@ -9,7 +9,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:dynamodb:region:account-id:certificate/resource-id",
"Severity": "medium",
"ResourceType": "AwsDynamoDbTable",
"ResourceType": "AwsDynamoDBTable",
"Description": "Check if DynamoDB tables point-in-time recovery (PITR) is enabled.",
"Risk": "If the DynamoDB Table does not have point-in-time recovery enabled, it is vulnerable to accidental write or delete operations.",
"RelatedUrl": "https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/PointInTimeRecovery_Howitworks.html",
@@ -9,7 +9,7 @@
"SubServiceName": "snapshot",
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
"Severity": "critical",
"ResourceType": "Other",
"ResourceType": "AwsEc2Snapshot",
"Description": "Ensure there are no EBS Snapshots set as Public.",
"Risk": "When you share a snapshot, you are giving others access to all of the data on the snapshot. Share snapshots only with people with whom you want to share all of your snapshot data.",
"RelatedUrl": "",
@@ -9,7 +9,7 @@
"SubServiceName": "snapshot",
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
"Severity": "medium",
"ResourceType": "Other",
"ResourceType": "AwsEc2Snapshot",
"Description": "Check if EBS snapshots are encrypted.",
"Risk": "Data encryption at rest prevents data visibility in the event of its unauthorized access or theft.",
"RelatedUrl": "",
@@ -6,9 +6,7 @@ from base64 import b64decode
from detect_secrets import SecretsCollection
from detect_secrets.settings import default_settings
from prowler.config.config import encoding_format_utf_8
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.lib.logger import logger
from prowler.providers.aws.services.ec2.ec2_client import ec2_client
@@ -25,23 +23,12 @@ class ec2_instance_secrets_user_data(Check):
if instance.user_data:
temp_user_data_file = tempfile.NamedTemporaryFile(delete=False)
user_data = b64decode(instance.user_data)
try:
if user_data[0:2] == b"\x1f\x8b": # GZIP magic number
user_data = zlib.decompress(
user_data, zlib.MAX_WBITS | 32
).decode(encoding_format_utf_8)
else:
user_data = user_data.decode(encoding_format_utf_8)
except UnicodeDecodeError as error:
logger.warning(
f"{instance.region} -- Unable to decode user data in EC2 instance {instance.id}: {error}"
)
continue
except Exception as error:
logger.error(
f"{instance.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
continue
if user_data[0:2] == b"\x1f\x8b": # GZIP magic number
user_data = zlib.decompress(
user_data, zlib.MAX_WBITS | 32
).decode("utf-8")
else:
user_data = user_data.decode("utf-8")
temp_user_data_file.write(
bytes(user_data, encoding="raw_unicode_escape")
@@ -1,7 +1,7 @@
{
"Provider": "aws",
"CheckID": "ec2_securitygroup_allow_wide_open_public_ipv4",
"CheckTitle": "Ensure no security groups allow ingress and egress from wide-open IP address with a mask between 0 and 24.",
"CheckTitle": "Ensure no security groups allow ingress from wide-open non-RFC1918 address.",
"CheckType": [
"Infrastructure Security"
],
@@ -10,7 +10,7 @@
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
"Severity": "high",
"ResourceType": "AwsEc2SecurityGroup",
"Description": "Ensure no security groups allow ingress and egress from ide-open IP address with a mask between 0 and 24.",
"Description": "Ensure no security groups allow ingress from wide-open non-RFC1918 address.",
"Risk": "If Security groups are not properly configured the attack surface is increased.",
"RelatedUrl": "",
"Remediation": {
@@ -28,7 +28,7 @@ class ec2_securitygroup_allow_wide_open_public_ipv4(Check):
for ingress_rule in security_group.ingress_rules:
for ipv4 in ingress_rule["IpRanges"]:
ip = ipaddress.ip_network(ipv4["CidrIp"])
# Check if IP is public if 0 < prefixlen < 24
# Check if IP is public according to RFC1918 and if 0 < prefixlen < 24
if (
ip.is_global
and ip.prefixlen < cidr_treshold
@@ -42,7 +42,7 @@ class ec2_securitygroup_allow_wide_open_public_ipv4(Check):
for egress_rule in security_group.egress_rules:
for ipv4 in egress_rule["IpRanges"]:
ip = ipaddress.ip_network(ipv4["CidrIp"])
# Check if IP is public if 0 < prefixlen < 24
# Check if IP is public according to RFC1918 and if 0 < prefixlen < 24
if (
ip.is_global
and ip.prefixlen < cidr_treshold
@@ -10,7 +10,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:ecr:region:account-id:resource-id",
"Severity": "medium",
"ResourceType": "Other",
"ResourceType": "AwsEcrRegistry",
"Description": "Check if ECR Registry has scan on push enabled",
"Risk": "Amazon ECR image scanning helps in identifying software vulnerabilities in your container images. Amazon ECR uses the Common Vulnerabilities and Exposures (CVEs) database from the open-source Clair project and provides a list of scan findings. ",
"RelatedUrl": "",
@@ -15,27 +15,23 @@ class ecr_repositories_scan_vulnerabilities_in_latest_image(Check):
for repository in registry.repositories:
# First check if the repository has images
if len(repository.images_details) > 0:
# We only want to check the latest image pushed that is scannable
# We only want to check the latest image pushed
image = repository.images_details[-1]
report = Check_Report_AWS(self.metadata())
report.region = repository.region
report.resource_id = repository.name
report.resource_arn = repository.arn
report.resource_tags = repository.tags
report.status = "PASS"
status_extended_prefix = f"ECR repository '{repository.name}' has scanned the {image.type} container image with digest '{image.latest_digest}' and tag '{image.latest_tag}' "
report.status_extended = (
status_extended_prefix + "without findings."
)
report.status_extended = f"ECR repository {repository.name} has imageTag {image.latest_tag} scanned without findings."
if not image.scan_findings_status:
report.status = "FAIL"
report.status_extended = (
status_extended_prefix + "without a scan."
)
report.status_extended = f"ECR repository {repository.name} has imageTag {image.latest_tag} without a scan."
elif image.scan_findings_status == "FAILED":
report.status = "FAIL"
report.status_extended = (
status_extended_prefix + "with scan status FAILED."
f"ECR repository {repository.name} with scan status FAILED."
)
elif (
image.scan_findings_status != "FAILED"
@@ -46,29 +42,20 @@ class ecr_repositories_scan_vulnerabilities_in_latest_image(Check):
and image.scan_findings_severity_count.critical
):
report.status = "FAIL"
report.status_extended = (
status_extended_prefix
+ f"with findings: CRITICAL->{image.scan_findings_severity_count.critical}."
)
report.status_extended = f"ECR repository {repository.name} has imageTag {image.latest_tag} scanned with findings: CRITICAL->{image.scan_findings_severity_count.critical}."
elif minimum_severity == "HIGH" and (
image.scan_findings_severity_count.critical
or image.scan_findings_severity_count.high
):
report.status = "FAIL"
report.status_extended = (
status_extended_prefix
+ f"with findings: CRITICAL->{image.scan_findings_severity_count.critical}, HIGH->{image.scan_findings_severity_count.high}."
)
report.status_extended = f"ECR repository {repository.name} has imageTag {image.latest_tag} scanned with findings: CRITICAL->{image.scan_findings_severity_count.critical}, HIGH->{image.scan_findings_severity_count.high}."
elif minimum_severity == "MEDIUM" and (
image.scan_findings_severity_count.critical
or image.scan_findings_severity_count.high
or image.scan_findings_severity_count.medium
):
report.status = "FAIL"
report.status_extended = (
status_extended_prefix
+ f"with findings: CRITICAL->{image.scan_findings_severity_count.critical}, HIGH->{image.scan_findings_severity_count.high}, MEDIUM->{image.scan_findings_severity_count.medium}."
)
report.status_extended = f"ECR repository {repository.name} has imageTag {image.latest_tag} scanned with findings: CRITICAL->{image.scan_findings_severity_count.critical}, HIGH->{image.scan_findings_severity_count.high}, MEDIUM->{image.scan_findings_severity_count.medium}."
findings.append(report)
+46 -146
View File
@@ -17,14 +17,14 @@ class ECR(AWSService):
super().__init__(__class__.__name__, audit_info)
self.registry_id = audit_info.audited_account
self.registries = {}
self.__threading_call__(self._describe_registries_and_repositories)
self.__threading_call__(self._describe_repository_policies)
self.__threading_call__(self._get_image_details)
self.__threading_call__(self._get_repository_lifecycle_policy)
self.__threading_call__(self._get_registry_scanning_configuration)
self.__threading_call__(self._list_tags_for_resource)
self.__threading_call__(self.__describe_registries_and_repositories__)
self.__threading_call__(self.__describe_repository_policies__)
self.__threading_call__(self.__get_image_details__)
self.__threading_call__(self.__get_repository_lifecycle_policy__)
self.__threading_call__(self.__get_registry_scanning_configuration__)
self.__threading_call__(self.__list_tags_for_resource__)
def _describe_registries_and_repositories(self, regional_client):
def __describe_registries_and_repositories__(self, regional_client):
logger.info("ECR - Describing registries and repositories...")
regional_registry_repositories = []
try:
@@ -64,7 +64,7 @@ class ECR(AWSService):
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
def _describe_repository_policies(self, regional_client):
def __describe_repository_policies__(self, regional_client):
logger.info("ECR - Describing repository policies...")
try:
if regional_client.region in self.registries:
@@ -91,7 +91,7 @@ class ECR(AWSService):
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
def _get_repository_lifecycle_policy(self, regional_client):
def __get_repository_lifecycle_policy__(self, regional_client):
logger.info("ECR - Getting repository lifecycle policy...")
try:
if regional_client.region in self.registries:
@@ -119,7 +119,7 @@ class ECR(AWSService):
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
def _get_image_details(self, regional_client):
def __get_image_details__(self, regional_client):
logger.info("ECR - Getting images details...")
try:
if regional_client.region in self.registries:
@@ -139,115 +139,55 @@ class ECR(AWSService):
# The following condition is required since sometimes
# the AWS ECR API returns None using the iterator
if image is not None:
artifact_media_type = image.get(
"artifactMediaType", None
)
tags = image.get("imageTags", [])
if ECR._is_artifact_scannable(
artifact_media_type, tags
):
severity_counts = None
last_scan_status = None
image_digest = image.get("imageDigest")
latest_tag = image.get("imageTags", ["None"])[0]
image_pushed_at = image.get("imagePushedAt")
image_scan_findings_field_name = (
severity_counts = None
last_scan_status = None
if "imageScanStatus" in image:
last_scan_status = image["imageScanStatus"][
"status"
]
if "imageScanFindingsSummary" in image:
severity_counts = FindingSeverityCounts(
critical=0, high=0, medium=0
)
finding_severity_counts = image[
"imageScanFindingsSummary"
)
if "docker" in artifact_media_type:
type = "Docker"
elif "oci" in artifact_media_type:
type = "OCI"
else:
type = ""
# If imageScanStatus is not present or imageScanFindingsSummary is missing,
# we need to call DescribeImageScanFindings because AWS' new version of
# basic scanning does not support imageScanFindingsSummary and imageScanStatus
# in the DescribeImages API.
if "imageScanStatus" not in image:
try:
# use "image" for scan findings to get data the same way as for an image
image = (
client.describe_image_scan_findings(
registryId=self.registries[
regional_client.region
].id,
repositoryName=repository.name,
imageId={
"imageDigest": image_digest
},
)
)
image_scan_findings_field_name = (
"imageScanFindings"
)
except (
client.exceptions.ImageNotFoundException
) as error:
logger.warning(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
continue
except (
client.exceptions.ScanNotFoundException
) as error:
logger.warning(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
continue
except Exception as error:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
continue
if "imageScanStatus" in image:
last_scan_status = image["imageScanStatus"][
"status"
]
if image_scan_findings_field_name in image:
severity_counts = FindingSeverityCounts(
critical=0, high=0, medium=0
)
finding_severity_counts = image[
image_scan_findings_field_name
].get("findingSeverityCounts", {})
]["findingSeverityCounts"]
if "CRITICAL" in finding_severity_counts:
severity_counts.critical = (
finding_severity_counts.get(
"CRITICAL", 0
)
finding_severity_counts["CRITICAL"]
)
if "HIGH" in finding_severity_counts:
severity_counts.high = (
finding_severity_counts.get("HIGH", 0)
finding_severity_counts["HIGH"]
)
if "MEDIUM" in finding_severity_counts:
severity_counts.medium = (
finding_severity_counts.get("MEDIUM", 0)
)
repository.images_details.append(
ImageDetails(
latest_tag=latest_tag,
image_pushed_at=image_pushed_at,
latest_digest=image_digest,
scan_findings_status=last_scan_status,
scan_findings_severity_count=severity_counts,
artifact_media_type=artifact_media_type,
type=type,
finding_severity_counts["MEDIUM"]
)
latest_tag = "None"
if image.get("imageTags"):
latest_tag = image["imageTags"][0]
repository.images_details.append(
ImageDetails(
latest_tag=latest_tag,
image_pushed_at=image["imagePushedAt"],
latest_digest=image["imageDigest"],
scan_findings_status=last_scan_status,
scan_findings_severity_count=severity_counts,
)
# Sort the repository images by date pushed
repository.images_details.sort(
key=lambda image: image.image_pushed_at
)
)
# Sort the repository images by date pushed
repository.images_details.sort(
key=lambda image: image.image_pushed_at
)
except Exception as error:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
def _list_tags_for_resource(self, regional_client):
def __list_tags_for_resource__(self, regional_client):
logger.info("ECR - List Tags...")
try:
if regional_client.region in self.registries:
@@ -275,7 +215,7 @@ class ECR(AWSService):
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
def _get_registry_scanning_configuration(self, regional_client):
def __get_registry_scanning_configuration__(self, regional_client):
logger.info("ECR - Getting Registry Scanning Configuration...")
try:
if regional_client.region in self.registries:
@@ -311,44 +251,6 @@ class ECR(AWSService):
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
@staticmethod
def _is_artifact_scannable(artifact_media_type: str, tags: list[str] = []) -> bool:
"""
Check if an artifact is scannable based on its media type and tags.
Args:
artifact_media_type (str): The media type of the artifact.
tags (list): The list of tags associated with the artifact.
Returns:
bool: True if the artifact is scannable, False otherwise.
"""
try:
if artifact_media_type is None:
return False
# Tools like GoogleContainerTools/jib uses `application/vnd.oci.image.config.v1+json`` also for signatures, which are not scannable.
# Luckily, these are tagged with sha-<HASH-CODE>.sig, so that they can still be easily recognized.
for tag in tags:
if tag.startswith("sha256-") and tag.endswith(".sig"):
return False
scannable_artifact_media_types = [
"application/vnd.docker.container.image.v1+json", # Docker image configuration
"application/vnd.docker.image.rootfs.diff.tar", # Docker image layer as a tar archive
"application/vnd.docker.image.rootfs.diff.tar.gzip", # Docker image layer that is compressed using gzip
"application/vnd.oci.image.config.v1+json", # OCI image configuration, but also used by GoogleContainerTools/jib for signatures
"application/vnd.oci.image.layer.v1.tar", # Uncompressed OCI image layer
"application/vnd.oci.image.layer.v1.tar+gzip", # Compressed OCI image layer
]
return artifact_media_type in scannable_artifact_media_types
except Exception as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
return False
class FindingSeverityCounts(BaseModel):
critical: int
@@ -362,8 +264,6 @@ class ImageDetails(BaseModel):
image_pushed_at: datetime
scan_findings_status: Optional[str]
scan_findings_severity_count: Optional[FindingSeverityCounts]
artifact_media_type: Optional[str]
type: str
class Repository(BaseModel):
@@ -15,9 +15,7 @@ class ECS(AWSService):
super().__init__(__class__.__name__, audit_info)
self.task_definitions = []
self.__threading_call__(self.__list_task_definitions__)
self.__threading_call__(
self.__describe_task_definition__, self.task_definitions
)
self.__describe_task_definition__()
def __list_task_definitions__(self, regional_client):
logger.info("ECS - Listing Task Definitions...")
@@ -43,27 +41,32 @@ class ECS(AWSService):
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
def __describe_task_definition__(self, task_definition):
def __describe_task_definition__(self):
logger.info("ECS - Describing Task Definitions...")
try:
client = self.regional_clients[task_definition.region]
response = client.describe_task_definition(
taskDefinition=task_definition.arn,
include=[
"TAGS",
],
)
container_definitions = response["taskDefinition"]["containerDefinitions"]
for container in container_definitions:
if "environment" in container:
for env_var in container["environment"]:
task_definition.environment_variables.append(
ContainerEnvVariable(
name=env_var["name"], value=env_var["value"]
for task_definition in self.task_definitions:
client = self.regional_clients[task_definition.region]
response = client.describe_task_definition(
taskDefinition=task_definition.arn,
include=[
"TAGS",
],
)
container_definitions = response["taskDefinition"][
"containerDefinitions"
]
for container in container_definitions:
if "environment" in container:
for env_var in container["environment"]:
task_definition.environment_variables.append(
ContainerEnvVariable(
name=env_var["name"], value=env_var["value"]
)
)
)
task_definition.tags = response.get("tags")
task_definition.network_mode = response["taskDefinition"].get("networkMode")
task_definition.tags = response.get("tags")
task_definition.network_mode = response["taskDefinition"].get(
"networkMode"
)
except Exception as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
@@ -11,7 +11,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
"Severity": "medium",
"ResourceType": "AwsEfsFileSystem",
"ResourceType": "AwsEFSFileSystem",
"Description": "Check if EFS protects sensitive data with encryption at rest",
"Risk": "EFS should be encrypted at rest to prevent exposure of sensitive data to bad actors",
"RelatedUrl": "",
@@ -11,7 +11,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
"Severity": "medium",
"ResourceType": "AwsEfsFileSystem",
"ResourceType": "AwsEFSFileSystem",
"Description": "Check if EFS File systems have backup enabled",
"Risk": "If backup is not enabled, data is vulnerable. Human error or bad actors could erase or modify data.",
"RelatedUrl": "",
@@ -10,7 +10,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
"Severity": "medium",
"ResourceType": "AwsEfsFileSystem",
"ResourceType": "AwsEFSFileSystem",
"Description": "Check if EFS have policies which allow access to any client within the VPC",
"Risk": "Restricting access to EFS file systems is a security best practice. Allowing access to any client within the VPC can lead to unauthorized access to the file system.",
"RelatedUrl": "",
@@ -7,7 +7,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
"Severity": "medium",
"ResourceType": "Other",
"ResourceType": "AWSElastiCacheCacheCluster",
"Description": "Ensure Elasticache Cluster is not using a public subnet",
"Risk": "There is a risk of exposing sensitive data if Elasticache Cluster uses a public subnet.",
"RelatedUrl": "https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/VPCs.html",
@@ -9,7 +9,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
"Severity": "medium",
"ResourceType": "AwsElbv2LoadBalancer",
"ResourceType": "AwsElasticLoadBalancingV2LoadBalancer",
"Description": "Check if Elastic Load Balancers have deletion protection enabled.",
"Risk": "If deletion protection is not enabled, the resource is not protected against deletion.",
"RelatedUrl": "",
@@ -9,7 +9,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
"Severity": "medium",
"ResourceType": "AwsElbv2LoadBalancer",
"ResourceType": "AwsElasticLoadBalancingV2LoadBalancer",
"Description": "Check whether the Application Load Balancer is configured with strictest desync mitigation mode, if not check if at least is configured with the drop_invalid_header_fields attribute",
"Risk": "HTTP Desync issues can lead to request smuggling and make your applications vulnerable to request queue or cache poisoning, which could lead to credential hijacking or execution of unauthorized commands.",
"RelatedUrl": "https://docs.aws.amazon.com/elasticloadbalancing/latest/application/application-load-balancers.html#desync-mitigation-mode",
@@ -9,7 +9,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
"Severity": "medium",
"ResourceType": "AwsElbv2LoadBalancer",
"ResourceType": "AwsElasticLoadBalancingV2LoadBalancer",
"Description": "Check if Elastic Load Balancers have insecure SSL ciphers.",
"Risk": "Using insecure ciphers could affect privacy of in transit information.",
"RelatedUrl": "",
@@ -9,7 +9,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
"Severity": "medium",
"ResourceType": "AwsElbv2LoadBalancer",
"ResourceType": "AwsElasticLoadBalancingV2LoadBalancer",
"Description": "Check for internet facing Elastic Load Balancers.",
"Risk": "Publicly accessible load balancers could expose sensitive data to bad actors.",
"RelatedUrl": "",
@@ -9,7 +9,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
"Severity": "medium",
"ResourceType": "AwsElbv2LoadBalancer",
"ResourceType": "AwsElasticLoadBalancingV2LoadBalancer",
"Description": "Check if ELBV2 has listeners underneath.",
"Risk": "The rules that are defined for a listener determine how the load balancer routes requests to its registered targets.",
"RelatedUrl": "",
@@ -9,7 +9,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
"Severity": "medium",
"ResourceType": "AwsElbv2LoadBalancer",
"ResourceType": "AwsElasticLoadBalancingV2LoadBalancer",
"Description": "Check if Elastic Load Balancers have logging enabled.",
"Risk": "If logs are not enabled monitoring of service use and threat analysis is not possible.",
"RelatedUrl": "",
@@ -9,7 +9,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
"Severity": "medium",
"ResourceType": "AwsElbv2LoadBalancer",
"ResourceType": "AwsElasticLoadBalancingV2LoadBalancer",
"Description": "Check if Elastic Load Balancers have SSL listeners.",
"Risk": "Clear text communication could affect privacy of information in transit.",
"RelatedUrl": "",
@@ -9,7 +9,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
"Severity": "medium",
"ResourceType": "AwsElbv2LoadBalancer",
"ResourceType": "AwsElasticLoadBalancingV2LoadBalancer",
"Description": "Check if Application Load Balancer has a WAF ACL attached.",
"Risk": "If not WAF ACL is attached risk of web attacks increases.",
"RelatedUrl": "",
@@ -7,7 +7,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:aws:emr:region:account-id",
"Severity": "high",
"ResourceType": "AwsAccount",
"ResourceType": "AwsEMR",
"Description": "EMR Account Public Access Block enabled.",
"Risk": "EMR Clusters must have Account Public Access Block enabled.",
"RelatedUrl": "https://docs.aws.amazon.com/emr/latest/ManagementGuide/emr-block-public-access.html",
@@ -7,7 +7,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:aws:emr:region:account-id",
"Severity": "medium",
"ResourceType": "Other",
"ResourceType": "AwsEMR",
"Description": "EMR Cluster without Public IP.",
"Risk": "EMR Cluster should not have Public IP.",
"RelatedUrl": "https://docs.aws.amazon.com/emr/latest/ManagementGuide/emr-block-public-access.html",
@@ -7,7 +7,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:aws:emr:region:account-id",
"Severity": "medium",
"ResourceType": "Other",
"ResourceType": "AwsEMR",
"Description": "Publicly accessible EMR Cluster.",
"Risk": "EMR Clusters should not be publicly accessible.",
"RelatedUrl": "https://docs.aws.amazon.com/emr/latest/ManagementGuide/emr-block-public-access.html",
@@ -7,7 +7,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:aws:glacier:region:account-id:vaults/vault-name",
"Severity": "critical",
"ResourceType": "Other",
"ResourceType": "AwsGlacierVault",
"Description": "Ensure CodeArtifact internal packages do not allow external public source publishing.",
"Risk": "Vaults accessible to everyone could expose sensitive data to bad actors.",
"RelatedUrl": "https://docs.aws.amazon.com/amazonglacier/latest/dev/access-control-overview.html",
@@ -9,7 +9,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:glue:region:account-id:certificate/resource-id",
"Severity": "medium",
"ResourceType": "Other",
"ResourceType": "AwsGlue",
"Description": "Check if Glue data catalog settings have encrypt connection password enabled.",
"Risk": "If not enabled sensitive information at rest is not protected.",
"RelatedUrl": "",
@@ -9,7 +9,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:glue:region:account-id:certificate/resource-id",
"Severity": "medium",
"ResourceType": "Other",
"ResourceType": "AwsGlue",
"Description": "Check if Glue data catalog settings have metadata encryption enabled.",
"Risk": "If not enabled sensitive information at rest is not protected.",
"RelatedUrl": "",
@@ -9,7 +9,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:glue:region:account-id:certificate/resource-id",
"Severity": "medium",
"ResourceType": "Other",
"ResourceType": "AwsGlue",
"Description": "Check if Glue database connection has SSL connection enabled.",
"Risk": "Data exfiltration could happen if information is not protected in transit.",
"RelatedUrl": "https://docs.aws.amazon.com/glue/latest/dg/encryption-in-transit.html",
@@ -9,7 +9,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:glue:region:account-id:certificate/resource-id",
"Severity": "medium",
"ResourceType": "Other",
"ResourceType": "AwsGlue",
"Description": "Check if Glue development endpoints have CloudWatch logs encryption enabled.",
"Risk": "If not enabled sensitive information at rest is not protected.",
"RelatedUrl": "https://docs.aws.amazon.com/glue/latest/dg/console-security-configurations.html",
@@ -9,7 +9,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:glue:region:account-id:certificate/resource-id",
"Severity": "medium",
"ResourceType": "Other",
"ResourceType": "AwsGlue",
"Description": "Check if Glue development endpoints have Job bookmark encryption enabled.",
"Risk": "If not enabled sensitive information at rest is not protected.",
"RelatedUrl": "https://docs.aws.amazon.com/glue/latest/dg/console-security-configurations.html",
@@ -9,7 +9,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:glue:region:account-id:certificate/resource-id",
"Severity": "medium",
"ResourceType": "Other",
"ResourceType": "AwsGlue",
"Description": "Check if Glue development endpoints have S3 encryption enabled.",
"Risk": "Data exfiltration could happen if information is not protected. KMS keys provide additional security level to IAM policies.",
"RelatedUrl": "https://docs.aws.amazon.com/glue/latest/dg/encryption-security-configuration.html",
@@ -9,7 +9,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:glue:region:account-id:certificate/resource-id",
"Severity": "medium",
"ResourceType": "AwsGlueJob",
"ResourceType": "AwsGlue",
"Description": "Check if Glue ETL Jobs have S3 encryption enabled.",
"Risk": "If not enabled sensitive information at rest is not protected.",
"RelatedUrl": "https://docs.aws.amazon.com/glue/latest/dg/console-security-configurations.html",
@@ -9,7 +9,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:glue:region:account-id:certificate/resource-id",
"Severity": "medium",
"ResourceType": "AwsGlueJob",
"ResourceType": "AwsGlue",
"Description": "Check if Glue ETL Jobs have CloudWatch Logs encryption enabled.",
"Risk": "If not enabled sensitive information at rest is not protected.",
"RelatedUrl": "https://docs.aws.amazon.com/glue/latest/dg/console-security-configurations.html",

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