mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-24 12:24:17 +00:00
feat(checks): PHP runtime coverage after the October security release (#13127)
Co-authored-by: Rubén De la Torre Vico <98956809+puchy22@users.noreply.github.com>
This commit is contained in:
1 parent
35d25a7218
commit
669df91ac9
63 files changed
+1928
-732
No files matched your search
@@ -164,6 +164,28 @@ mainConfig:
|
||||
# aws.appstream_fleet_maximum_session_duration
|
||||
max_session_duration_seconds: 36000 # 10 Hours
|
||||
|
||||
# AWS Elastic Beanstalk Configuration
|
||||
# aws.elasticbeanstalk_environment_php_platform_updated
|
||||
# Oldest PHP branch still receiving security fixes
|
||||
elasticbeanstalk_php_min_branch: "8.2"
|
||||
# Oldest Amazon Linux 2023 PHP platform version bundling the fixed PHP release
|
||||
elasticbeanstalk_php_min_platform_version: "4.13.13"
|
||||
|
||||
# AWS Lightsail Configuration
|
||||
# aws.lightsail_instance_php_blueprint_updated
|
||||
# Blueprint id prefixes that ship PHP and require manual runtime verification
|
||||
lightsail_php_blueprints:
|
||||
[
|
||||
"wordpress",
|
||||
"lamp",
|
||||
"nginx",
|
||||
"drupal",
|
||||
"joomla",
|
||||
"magento",
|
||||
"prestashop",
|
||||
"plesk",
|
||||
"cpanel",
|
||||
]
|
||||
# AWS Lambda Configuration
|
||||
# aws.awslambda_function_using_supported_runtimes
|
||||
obsolete_lambda_runtimes:
|
||||
@@ -501,6 +523,7 @@ mainConfig:
|
||||
shodan_api_key: null
|
||||
|
||||
# Azure App Service
|
||||
# Minimum runtime versions; any newer version passes
|
||||
# azure.app_ensure_php_version_is_latest
|
||||
php_latest_version: "8.5"
|
||||
# azure.app_ensure_python_version_is_latest
|
||||
|
||||
@@ -39,6 +39,9 @@ The following list includes all the AWS checks with configurable variables that
|
||||
| `awslambda_function_no_secrets_in_variables` | `secrets_ignore_patterns` | List of Strings | `[]` |
|
||||
| `awslambda_function_using_supported_runtimes` | `obsolete_lambda_runtimes` | List of Strings | See `config.yaml` |
|
||||
| `awslambda_function_vpc_multi_az` | `lambda_min_azs` | Integer | `2` |
|
||||
| `elasticbeanstalk_environment_php_platform_updated` | `elasticbeanstalk_php_min_branch` | String | `"8.2"` |
|
||||
| `elasticbeanstalk_environment_php_platform_updated` | `elasticbeanstalk_php_min_platform_version` | String | `"4.13.13"` |
|
||||
| `lightsail_instance_php_blueprint_updated` | `lightsail_php_blueprints` | List of Strings | See `config.yaml` |
|
||||
| `cloudformation_stack_cdktoolkit_bootstrap_version` | `recommended_cdk_bootstrap_version` | Integer | `21` |
|
||||
| `cloudformation_stack_outputs_find_secrets` | `secrets_ignore_patterns` | List of Strings | `[]` |
|
||||
| `cloudfront_distributions_pqc_tls_enabled` | `cloudfront_pqc_min_protocol_versions` | List of Strings | `["TLSv1.3_2025"]` |
|
||||
@@ -231,6 +234,7 @@ The following list includes all the GCP checks with configurable variables that
|
||||
|
||||
| Check Name | Value | Type | Default |
|
||||
|---------------------------------------------------|-----------------------------------|---------|---------|
|
||||
| `appengine_version_php_runtime_supported` | `appengine_php_min_version` | String | `"8.2"` |
|
||||
| `cloudstorage_bucket_sufficient_retention_period` | `storage_min_retention_days` | Integer | `90` |
|
||||
| `compute_instance_group_multiple_zones` | `mig_min_zones` | Integer | `2` |
|
||||
| `compute_public_address_shodan` | `shodan_api_key` | String | `null` |
|
||||
@@ -448,6 +452,28 @@ aws:
|
||||
# aws.appstream_fleet_maximum_session_duration
|
||||
max_session_duration_seconds: 36000 # 10 Hours
|
||||
|
||||
# AWS Elastic Beanstalk Configuration
|
||||
# aws.elasticbeanstalk_environment_php_platform_updated
|
||||
# Oldest PHP branch still receiving security fixes
|
||||
elasticbeanstalk_php_min_branch: "8.2"
|
||||
# Oldest Amazon Linux 2023 PHP platform version bundling the fixed PHP release
|
||||
elasticbeanstalk_php_min_platform_version: "4.13.13"
|
||||
|
||||
# AWS Lightsail Configuration
|
||||
# aws.lightsail_instance_php_blueprint_updated
|
||||
# Blueprint id prefixes that ship PHP and require manual runtime verification
|
||||
lightsail_php_blueprints:
|
||||
[
|
||||
"wordpress",
|
||||
"lamp",
|
||||
"nginx",
|
||||
"drupal",
|
||||
"joomla",
|
||||
"magento",
|
||||
"prestashop",
|
||||
"plesk",
|
||||
"cpanel",
|
||||
]
|
||||
# AWS Lambda Configuration
|
||||
# aws.awslambda_function_using_supported_runtimes
|
||||
obsolete_lambda_runtimes:
|
||||
@@ -718,6 +744,7 @@ azure:
|
||||
shodan_api_key: null
|
||||
|
||||
# Azure App Service
|
||||
# Minimum runtime versions; any newer version passes
|
||||
# azure.app_ensure_php_version_is_latest
|
||||
php_latest_version: "8.5"
|
||||
# azure.app_ensure_python_version_is_latest
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
`elasticbeanstalk_environment_php_platform_updated` check to flag Elastic Beanstalk PHP environments on branches without security fixes or on platform versions older than the one bundling the fixed PHP release
|
||||
@@ -0,0 +1 @@
|
||||
`lightsail_instance_php_blueprint_updated` check to flag Lightsail instances created from PHP blueprints for manual verification of the PHP release
|
||||
@@ -0,0 +1 @@
|
||||
`wafv2_webacl_managed_rule_groups_enabled` check to verify WAFv2 Web ACLs reference at least one managed rule group
|
||||
@@ -0,0 +1 @@
|
||||
Azure App Service runtime checks (`app_ensure_php_version_is_latest`, `app_ensure_python_version_is_latest`, `app_ensure_java_version_is_latest`) now pass any runtime at or above the configured minimum instead of failing every version newer than the configured one
|
||||
@@ -0,0 +1 @@
|
||||
`appengine` GCP service and `appengine_version_php_runtime_supported` check to flag App Engine serving versions on PHP runtimes without security fixes
|
||||
@@ -123,7 +123,8 @@
|
||||
"inspector2_active_findings_exist",
|
||||
"awslambda_function_using_supported_runtimes",
|
||||
"rds_instance_deprecated_engine_version",
|
||||
"rds_instance_extended_support"
|
||||
"rds_instance_extended_support",
|
||||
"elasticbeanstalk_environment_php_platform_updated"
|
||||
],
|
||||
"Attributes": [
|
||||
{
|
||||
@@ -160,7 +161,8 @@
|
||||
"redshift_cluster_automatic_upgrades",
|
||||
"kafka_cluster_uses_latest_version",
|
||||
"opensearch_service_domains_updated_to_the_latest_service_software_version",
|
||||
"ecs_service_fargate_latest_platform_version"
|
||||
"ecs_service_fargate_latest_platform_version",
|
||||
"elasticbeanstalk_environment_php_platform_updated"
|
||||
],
|
||||
"Attributes": [
|
||||
{
|
||||
@@ -215,7 +217,8 @@
|
||||
"eks_cluster_uses_a_supported_version",
|
||||
"ecs_service_fargate_latest_platform_version",
|
||||
"kafka_cluster_uses_latest_version",
|
||||
"opensearch_service_domains_updated_to_the_latest_service_software_version"
|
||||
"opensearch_service_domains_updated_to_the_latest_service_software_version",
|
||||
"elasticbeanstalk_environment_php_platform_updated"
|
||||
],
|
||||
"Attributes": [
|
||||
{
|
||||
|
||||
@@ -60,7 +60,8 @@
|
||||
"wafv2_webacl_logging_enabled",
|
||||
"wafv2_webacl_rule_logging_enabled",
|
||||
"apigateway_restapi_waf_acl_attached",
|
||||
"cognito_user_pool_waf_acl_attached"
|
||||
"cognito_user_pool_waf_acl_attached",
|
||||
"wafv2_webacl_managed_rule_groups_enabled"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -5142,7 +5142,8 @@
|
||||
"ssm_managed_compliant_patching",
|
||||
"elasticache_redis_cluster_auto_minor_version_upgrades",
|
||||
"codeartifact_packages_external_public_publishing_disabled",
|
||||
"elasticbeanstalk_environment_managed_updates_enabled"
|
||||
"elasticbeanstalk_environment_managed_updates_enabled",
|
||||
"elasticbeanstalk_environment_php_platform_updated"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -2856,7 +2856,8 @@
|
||||
"waf_regional_webacl_with_rules",
|
||||
"wafv2_webacl_logging_enabled",
|
||||
"wafv2_webacl_rule_logging_enabled",
|
||||
"wafv2_webacl_with_rules"
|
||||
"wafv2_webacl_with_rules",
|
||||
"wafv2_webacl_managed_rule_groups_enabled"
|
||||
],
|
||||
"ConfigRequirements": [
|
||||
{
|
||||
@@ -3619,7 +3620,8 @@
|
||||
"rds_instance_deprecated_engine_version",
|
||||
"rds_instance_minor_version_upgrade_enabled",
|
||||
"redshift_cluster_automatic_upgrades",
|
||||
"ssm_managed_compliant_patching"
|
||||
"ssm_managed_compliant_patching",
|
||||
"elasticbeanstalk_environment_php_platform_updated"
|
||||
],
|
||||
"Attributes": [
|
||||
{
|
||||
|
||||
@@ -2859,7 +2859,8 @@
|
||||
"waf_regional_webacl_with_rules",
|
||||
"wafv2_webacl_logging_enabled",
|
||||
"wafv2_webacl_rule_logging_enabled",
|
||||
"wafv2_webacl_with_rules"
|
||||
"wafv2_webacl_with_rules",
|
||||
"wafv2_webacl_managed_rule_groups_enabled"
|
||||
],
|
||||
"ConfigRequirements": [
|
||||
{
|
||||
@@ -3622,7 +3623,8 @@
|
||||
"rds_instance_deprecated_engine_version",
|
||||
"rds_instance_minor_version_upgrade_enabled",
|
||||
"redshift_cluster_automatic_upgrades",
|
||||
"ssm_managed_compliant_patching"
|
||||
"ssm_managed_compliant_patching",
|
||||
"elasticbeanstalk_environment_php_platform_updated"
|
||||
],
|
||||
"Attributes": [
|
||||
{
|
||||
|
||||
@@ -1128,7 +1128,8 @@
|
||||
"Description": "requirements regarding security updates throughout the entire lifetime of the ICT services or ICT products, or replacement after the end of the support period;",
|
||||
"Checks": [
|
||||
"elasticbeanstalk_environment_managed_updates_enabled",
|
||||
"opensearch_service_domains_updated_to_the_latest_service_software_version"
|
||||
"opensearch_service_domains_updated_to_the_latest_service_software_version",
|
||||
"elasticbeanstalk_environment_php_platform_updated"
|
||||
],
|
||||
"Attributes": [
|
||||
{
|
||||
@@ -1253,7 +1254,8 @@
|
||||
"memorydb_cluster_auto_minor_version_upgrades",
|
||||
"elasticbeanstalk_environment_managed_updates_enabled",
|
||||
"elasticache_redis_cluster_auto_minor_version_upgrades",
|
||||
"glue_development_endpoints_job_bookmark_encryption_enabled"
|
||||
"glue_development_endpoints_job_bookmark_encryption_enabled",
|
||||
"elasticbeanstalk_environment_php_platform_updated"
|
||||
],
|
||||
"Attributes": [
|
||||
{
|
||||
|
||||
@@ -280,7 +280,11 @@
|
||||
"ec2_instance_with_outdated_ami",
|
||||
"eks_cluster_uses_a_supported_version",
|
||||
"kafka_cluster_uses_latest_version",
|
||||
"rds_instance_deprecated_engine_version"
|
||||
"rds_instance_deprecated_engine_version",
|
||||
"elasticbeanstalk_environment_php_platform_updated"
|
||||
],
|
||||
"gcp": [
|
||||
"appengine_version_php_runtime_supported"
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -3717,7 +3721,8 @@
|
||||
"waf_regional_rule_with_conditions",
|
||||
"waf_regional_rulegroup_not_empty",
|
||||
"waf_regional_webacl_with_rules",
|
||||
"wafv2_webacl_with_rules"
|
||||
"wafv2_webacl_with_rules",
|
||||
"wafv2_webacl_managed_rule_groups_enabled"
|
||||
],
|
||||
"cloudflare": [
|
||||
"dns_record_proxied",
|
||||
|
||||
@@ -6363,7 +6363,8 @@
|
||||
"cloudfront_distributions_using_waf",
|
||||
"guardduty_is_enabled",
|
||||
"shield_advanced_protection_in_cloudfront_distributions",
|
||||
"shield_advanced_protection_in_internet_facing_load_balancers"
|
||||
"shield_advanced_protection_in_internet_facing_load_balancers",
|
||||
"wafv2_webacl_managed_rule_groups_enabled"
|
||||
],
|
||||
"azure": [
|
||||
"defender_ensure_defender_for_arm_is_on",
|
||||
@@ -8481,7 +8482,8 @@
|
||||
"memorydb_cluster_auto_minor_version_upgrades",
|
||||
"mq_broker_auto_minor_version_upgrades",
|
||||
"opensearch_service_domains_updated_to_the_latest_service_software_version",
|
||||
"kafka_cluster_uses_latest_version"
|
||||
"kafka_cluster_uses_latest_version",
|
||||
"elasticbeanstalk_environment_php_platform_updated"
|
||||
],
|
||||
"azure": [
|
||||
"app_ensure_java_version_is_latest",
|
||||
@@ -8492,6 +8494,9 @@
|
||||
],
|
||||
"alibabacloud": [
|
||||
"ecs_instance_latest_os_patches_applied"
|
||||
],
|
||||
"gcp": [
|
||||
"appengine_version_php_runtime_supported"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -354,7 +354,8 @@
|
||||
"kafka_cluster_uses_latest_version",
|
||||
"opensearch_service_domains_updated_to_the_latest_service_software_version",
|
||||
"rds_instance_deprecated_engine_version",
|
||||
"wellarchitected_workload_no_high_or_medium_risks"
|
||||
"wellarchitected_workload_no_high_or_medium_risks",
|
||||
"elasticbeanstalk_environment_php_platform_updated"
|
||||
],
|
||||
"azure": [],
|
||||
"gcp": [],
|
||||
@@ -718,7 +719,8 @@
|
||||
"waf_regional_rule_with_conditions",
|
||||
"waf_regional_rulegroup_not_empty",
|
||||
"waf_regional_webacl_with_rules",
|
||||
"wafv2_webacl_with_rules"
|
||||
"wafv2_webacl_with_rules",
|
||||
"wafv2_webacl_managed_rule_groups_enabled"
|
||||
],
|
||||
"azure": [
|
||||
"network_vnet_ddos_protection_enabled",
|
||||
@@ -1756,7 +1758,8 @@
|
||||
"ecr_registry_enhanced_scanning_enabled",
|
||||
"inspector2_active_findings_exist",
|
||||
"inspector2_is_enabled",
|
||||
"ssm_managed_compliant_patching"
|
||||
"ssm_managed_compliant_patching",
|
||||
"elasticbeanstalk_environment_php_platform_updated"
|
||||
],
|
||||
"azure": [
|
||||
"app_ensure_java_version_is_latest",
|
||||
@@ -1765,7 +1768,9 @@
|
||||
"app_function_latest_runtime_version",
|
||||
"defender_ensure_system_updates_are_applied"
|
||||
],
|
||||
"gcp": [],
|
||||
"gcp": [
|
||||
"appengine_version_php_runtime_supported"
|
||||
],
|
||||
"kubernetes": [],
|
||||
"m365": []
|
||||
},
|
||||
|
||||
@@ -4427,7 +4427,9 @@
|
||||
"ComplementaryCriteria": ""
|
||||
}
|
||||
],
|
||||
"Checks": []
|
||||
"Checks": [
|
||||
"appengine_version_php_runtime_supported"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "OPS-33.01AS",
|
||||
|
||||
@@ -656,7 +656,8 @@
|
||||
"apikeys_api_restrictions_configured",
|
||||
"apikeys_key_rotated_in_90_days",
|
||||
"artifacts_container_analysis_enabled",
|
||||
"bigquery_dataset_cmk_encryption"
|
||||
"bigquery_dataset_cmk_encryption",
|
||||
"appengine_version_php_runtime_supported"
|
||||
],
|
||||
"Attributes": [
|
||||
{
|
||||
@@ -733,7 +734,8 @@
|
||||
"Checks": [
|
||||
"compute_instance_shielded_vm_enabled",
|
||||
"gcr_container_scanning_enabled",
|
||||
"artifacts_container_analysis_enabled"
|
||||
"artifacts_container_analysis_enabled",
|
||||
"appengine_version_php_runtime_supported"
|
||||
],
|
||||
"Attributes": [
|
||||
{
|
||||
|
||||
@@ -81,7 +81,8 @@
|
||||
"compute_instance_shielded_vm_enabled",
|
||||
"compute_project_os_login_enabled",
|
||||
"artifacts_container_analysis_enabled",
|
||||
"gcr_container_scanning_enabled"
|
||||
"gcr_container_scanning_enabled",
|
||||
"appengine_version_php_runtime_supported"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -139,6 +139,28 @@ aws:
|
||||
# aws.appstream_fleet_maximum_session_duration
|
||||
max_session_duration_seconds: 36000 # 10 Hours
|
||||
|
||||
# AWS Elastic Beanstalk Configuration
|
||||
# aws.elasticbeanstalk_environment_php_platform_updated
|
||||
# Oldest PHP branch still receiving security fixes
|
||||
elasticbeanstalk_php_min_branch: "8.2"
|
||||
# Oldest Amazon Linux 2023 PHP platform version bundling the fixed PHP release
|
||||
elasticbeanstalk_php_min_platform_version: "4.13.13"
|
||||
|
||||
# AWS Lightsail Configuration
|
||||
# aws.lightsail_instance_php_blueprint_updated
|
||||
# Blueprint id prefixes that ship PHP and require manual runtime verification
|
||||
lightsail_php_blueprints:
|
||||
[
|
||||
"wordpress",
|
||||
"lamp",
|
||||
"nginx",
|
||||
"drupal",
|
||||
"joomla",
|
||||
"magento",
|
||||
"prestashop",
|
||||
"plesk",
|
||||
"cpanel",
|
||||
]
|
||||
# AWS Lambda Configuration
|
||||
# aws.awslambda_function_using_supported_runtimes
|
||||
obsolete_lambda_runtimes:
|
||||
@@ -550,6 +572,7 @@ azure:
|
||||
defender_attack_path_minimal_risk_level: "High"
|
||||
|
||||
# Azure App Service
|
||||
# Minimum runtime versions; any newer version passes
|
||||
# azure.app_ensure_php_version_is_latest
|
||||
php_latest_version: "8.5"
|
||||
# azure.app_ensure_python_version_is_latest
|
||||
@@ -650,6 +673,10 @@ gcp:
|
||||
# GCP Secret Manager Rotation Period
|
||||
# gcp.secretmanager_secret_rotation_enabled
|
||||
secretmanager_max_rotation_days: 90
|
||||
# GCP App Engine PHP runtime
|
||||
# gcp.appengine_version_php_runtime_supported
|
||||
# Oldest PHP branch still receiving security fixes
|
||||
appengine_php_min_version: "8.2"
|
||||
|
||||
# Kubernetes Configuration
|
||||
kubernetes:
|
||||
|
||||
@@ -88,6 +88,8 @@ _validate_trusted_ips = validate_ip_networks
|
||||
_validate_semver = make_dotted_version_validator(3, 3)
|
||||
# "1.28" style strings (EKS minor versions).
|
||||
_validate_eks_minor = make_dotted_version_validator(2, 2)
|
||||
_validate_php_branch = make_dotted_version_validator(2, 2)
|
||||
_validate_platform_version = make_dotted_version_validator(3, 3)
|
||||
|
||||
|
||||
def _validate_account_ids(v: Optional[list[str]]) -> Optional[list[str]]:
|
||||
@@ -309,6 +311,20 @@ class AWSProviderConfig(ProviderConfigBase):
|
||||
),
|
||||
)
|
||||
|
||||
# --- Elastic Beanstalk ------------------------------------------------
|
||||
elasticbeanstalk_php_min_branch: Annotated[
|
||||
Optional[str], AfterValidator(_validate_php_branch)
|
||||
] = Field(
|
||||
default=None,
|
||||
description='Oldest PHP branch still receiving security fixes, e.g. "8.2".',
|
||||
)
|
||||
elasticbeanstalk_php_min_platform_version: Annotated[
|
||||
Optional[str], AfterValidator(_validate_platform_version)
|
||||
] = Field(
|
||||
default=None,
|
||||
description='Oldest Amazon Linux 2023 PHP platform version bundling the fixed PHP release, e.g. "4.13.13".',
|
||||
)
|
||||
|
||||
# --- Lambda -----------------------------------------------------------
|
||||
obsolete_lambda_runtimes: Optional[list[str]] = None
|
||||
lambda_min_azs: Optional[int] = Field(
|
||||
@@ -318,6 +334,12 @@ class AWSProviderConfig(ProviderConfigBase):
|
||||
description="Min number of AZs a VPC-bound Lambda must span. Range: 1..6.",
|
||||
)
|
||||
|
||||
# --- Lightsail --------------------------------------------------------
|
||||
lightsail_php_blueprints: Optional[list[str]] = Field(
|
||||
default=None,
|
||||
description="Blueprint id prefixes that ship PHP and require manual runtime verification.",
|
||||
)
|
||||
|
||||
# --- Organizations ----------------------------------------------------
|
||||
organizations_enabled_regions: Optional[list[str]] = None
|
||||
organizations_trusted_delegated_administrators: Annotated[
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
"""GCP provider config schema with safety bounds."""
|
||||
|
||||
from typing import Optional
|
||||
from typing import Annotated, Optional
|
||||
|
||||
from pydantic import Field
|
||||
from pydantic import AfterValidator, Field
|
||||
|
||||
from prowler.config.schema.base import ProviderConfigBase
|
||||
from prowler.config.schema.validators import make_dotted_version_validator
|
||||
|
||||
_validate_php_branch = make_dotted_version_validator(2, 2)
|
||||
|
||||
|
||||
class GCPProviderConfig(ProviderConfigBase):
|
||||
@@ -43,3 +46,11 @@ class GCPProviderConfig(ProviderConfigBase):
|
||||
le=3650,
|
||||
description="Min retention period on Cloud Storage buckets. Range: 1..3650.",
|
||||
)
|
||||
|
||||
# --- App Engine -----------------------------------------------------
|
||||
appengine_php_min_version: Annotated[
|
||||
Optional[str], AfterValidator(_validate_php_branch)
|
||||
] = Field(
|
||||
default=None,
|
||||
description='Oldest PHP branch still receiving security fixes, e.g. "8.2".',
|
||||
)
|
||||
Whitespace-only changes.
+44
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"Provider": "aws",
|
||||
"CheckID": "elasticbeanstalk_environment_php_platform_updated",
|
||||
"CheckTitle": "Elastic Beanstalk PHP environment runs a platform version with the fixed PHP release",
|
||||
"CheckType": [
|
||||
"Software and Configuration Checks/Patch Management",
|
||||
"Software and Configuration Checks/Vulnerabilities/CVE"
|
||||
],
|
||||
"ServiceName": "elasticbeanstalk",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "critical",
|
||||
"ResourceType": "AwsElasticBeanstalkEnvironment",
|
||||
"ResourceGroup": "compute",
|
||||
"Description": "**Elastic Beanstalk PHP environments** are evaluated to confirm they run a supported PHP branch (`elasticbeanstalk_php_min_branch`, `8.2` by default) on an **Amazon Linux 2023 platform version** that bundles the fixed PHP release (`elasticbeanstalk_php_min_platform_version`, `4.13.13` by default). The platform family, version and PHP branch are read from the environment's solution stack name.",
|
||||
"Risk": "PHP platform versions published before the fixed release bundle a PHP runtime vulnerable to **unauthenticated remote code execution** through crafted web requests, giving attackers code execution with the web worker's privileges, access to application data and the ability to crash the environment. Branches that reached end of life never receive the fix.",
|
||||
"RelatedUrl": "",
|
||||
"AdditionalURLs": [
|
||||
"https://docs.aws.amazon.com/elasticbeanstalk/latest/relnotes/release-2026-10-21-al2023-php84-php85.html",
|
||||
"https://docs.aws.amazon.com/elasticbeanstalk/latest/relnotes/release-2026-10-22-al2023-php82-php83.html",
|
||||
"https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/using-features.platform.upgrade.html",
|
||||
"https://www.php.net/supported-versions.php"
|
||||
],
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "aws elasticbeanstalk update-environment --environment-name <environment-name> --solution-stack-name \"64bit Amazon Linux 2023 v4.13.13 running PHP 8.4\"",
|
||||
"NativeIaC": "```yaml\nResources:\n <example_resource_name>:\n Type: AWS::ElasticBeanstalk::Environment\n Properties:\n ApplicationName: <example_resource_name>\n SolutionStackName: \"64bit Amazon Linux 2023 v4.13.13 running PHP 8.4\" # Critical: platform version with the fixed PHP release\n```",
|
||||
"Other": "1. Open the AWS Management Console and go to Elastic Beanstalk\n2. Select the environment and open its dashboard\n3. Under Platform, choose Change version\n4. Select platform version 4.13.13 or newer for a supported PHP branch (8.2 or newer)\n5. Click Save and wait for the environment update to finish",
|
||||
"Terraform": "```hcl\nresource \"aws_elastic_beanstalk_environment\" \"<example_resource_name>\" {\n name = \"<example_resource_name>\"\n application = \"<example_resource_name>\"\n solution_stack_name = \"64bit Amazon Linux 2023 v4.13.13 running PHP 8.4\" # Critical: platform version with the fixed PHP release\n}\n```"
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Update every PHP environment to the latest Amazon Linux 2023 platform version and keep **managed platform updates** enabled so future security releases are applied automatically. Move environments still on PHP 8.1 or older to a supported branch, since those branches receive no fixes. Validate in staging and roll out with a maintenance window.",
|
||||
"Url": "https://hub.prowler.com/check/elasticbeanstalk_environment_php_platform_updated"
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"vulnerabilities"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [
|
||||
"elasticbeanstalk_environment_managed_updates_enabled"
|
||||
],
|
||||
"Notes": "Only environments whose solution stack runs PHP are evaluated. Platform versions are compared only within the Amazon Linux 2023 family; older families are reported as failing because they did not receive fixed PHP releases."
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
import re
|
||||
|
||||
from prowler.lib.check.models import Check, Check_Report_AWS
|
||||
from prowler.providers.aws.services.elasticbeanstalk.elasticbeanstalk_client import (
|
||||
elasticbeanstalk_client,
|
||||
)
|
||||
|
||||
# "64bit Amazon Linux 2023 v4.13.13 running PHP 8.4"
|
||||
SOLUTION_STACK_RE = re.compile(
|
||||
r"^(?P<family>.+?) v(?P<version>\d+(?:\.\d+)+) running PHP (?P<branch>\d+\.\d+)",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
# "arn:aws:elasticbeanstalk:us-east-1::platform/PHP 8.4 running on 64bit Amazon Linux 2023/4.13.13"
|
||||
PLATFORM_ARN_RE = re.compile(
|
||||
r"platform/PHP (?P<branch>\d+\.\d+) running on (?P<family>[^/]+)/(?P<version>\d+(?:\.\d+)+)",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
AMAZON_LINUX_YEAR_RE = re.compile(r"Amazon Linux (\d{4})")
|
||||
# First Amazon Linux generation that received platform versions with the fixed PHP release
|
||||
FIXED_PLATFORM_YEAR = 2023
|
||||
|
||||
|
||||
def _version_tuple(value: str) -> tuple[int, ...]:
|
||||
return tuple(int(part) for part in value.split(".")) if value else ()
|
||||
|
||||
|
||||
def _family_has_fixed_releases(family: str) -> bool:
|
||||
match = AMAZON_LINUX_YEAR_RE.search(family)
|
||||
return bool(match) and int(match.group(1)) >= FIXED_PLATFORM_YEAR
|
||||
|
||||
|
||||
def _php_platform(environment) -> tuple[str, str, str] | None:
|
||||
"""Return (branch, family, version) for PHP environments, None otherwise."""
|
||||
for pattern, value in (
|
||||
(SOLUTION_STACK_RE, environment.solution_stack_name),
|
||||
(PLATFORM_ARN_RE, environment.platform_arn),
|
||||
):
|
||||
match = pattern.search(value or "")
|
||||
if match:
|
||||
return match["branch"], match["family"], match["version"]
|
||||
return None
|
||||
|
||||
|
||||
class elasticbeanstalk_environment_php_platform_updated(Check):
|
||||
def execute(self):
|
||||
findings = []
|
||||
# `or` covers an explicit null in the config file, which .get() would return as None
|
||||
min_php_branch = (
|
||||
elasticbeanstalk_client.audit_config.get("elasticbeanstalk_php_min_branch")
|
||||
or "8.2"
|
||||
)
|
||||
min_platform_version = (
|
||||
elasticbeanstalk_client.audit_config.get(
|
||||
"elasticbeanstalk_php_min_platform_version"
|
||||
)
|
||||
or "4.13.13"
|
||||
)
|
||||
|
||||
for environment in elasticbeanstalk_client.environments.values():
|
||||
php_platform = _php_platform(environment)
|
||||
if not php_platform:
|
||||
continue
|
||||
|
||||
php_branch, family, platform_version = php_platform
|
||||
stack = environment.solution_stack_name or environment.platform_arn
|
||||
|
||||
report = Check_Report_AWS(metadata=self.metadata(), resource=environment)
|
||||
report.status = "FAIL"
|
||||
|
||||
if _version_tuple(php_branch) < _version_tuple(min_php_branch):
|
||||
report.status_extended = f"Elastic Beanstalk environment {environment.name} runs PHP {php_branch}, a branch that no longer receives security fixes (minimum supported branch is {min_php_branch})."
|
||||
elif not _family_has_fixed_releases(family):
|
||||
report.status_extended = f"Elastic Beanstalk environment {environment.name} runs PHP {php_branch} on platform '{stack}', a platform family without fixed PHP releases; migrate it to a PHP {php_branch} Amazon Linux 2023 platform version {min_platform_version} or newer."
|
||||
elif _version_tuple(platform_version) < _version_tuple(
|
||||
min_platform_version
|
||||
):
|
||||
report.status_extended = f"Elastic Beanstalk environment {environment.name} runs PHP {php_branch} on platform version {platform_version}, below the minimum fixed platform version {min_platform_version}."
|
||||
else:
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"Elastic Beanstalk environment {environment.name} runs PHP {php_branch} on platform version {platform_version}, which includes the fixed PHP release."
|
||||
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
@@ -39,6 +39,10 @@ class ElasticBeanstalk(AWSService):
|
||||
application_name=environment["ApplicationName"],
|
||||
name=environment["EnvironmentName"],
|
||||
region=regional_client.region,
|
||||
platform_arn=environment.get("PlatformArn", ""),
|
||||
solution_stack_name=environment.get(
|
||||
"SolutionStackName", ""
|
||||
),
|
||||
)
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
@@ -121,6 +125,8 @@ class Environment(BaseModel):
|
||||
arn: str
|
||||
region: str
|
||||
application_name: str
|
||||
platform_arn: str = ""
|
||||
solution_stack_name: str = ""
|
||||
health_reporting: Optional[str]
|
||||
managed_platform_updates: Optional[str]
|
||||
cloudwatch_stream_logs: Optional[str]
|
||||
|
||||
Whitespace-only changes.
+43
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"Provider": "aws",
|
||||
"CheckID": "lightsail_instance_php_blueprint_updated",
|
||||
"CheckTitle": "Lightsail instance created from a PHP blueprint runs a fixed PHP release",
|
||||
"CheckType": [
|
||||
"Software and Configuration Checks/Patch Management",
|
||||
"Software and Configuration Checks/Vulnerabilities/CVE"
|
||||
],
|
||||
"ServiceName": "lightsail",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "arn:partition:lightsail:region:account-id:Instance/resource-id",
|
||||
"Severity": "high",
|
||||
"ResourceType": "Other",
|
||||
"ResourceGroup": "compute",
|
||||
"Description": "**Lightsail instances** created from blueprints that ship PHP (WordPress, WordPress Multisite, LAMP, Nginx and other PHP applications, configurable through `lightsail_php_blueprints`) are flagged for manual verification, because the PHP runtime inside the instance is not visible through the Lightsail API and running instances are not patched in place.",
|
||||
"Risk": "A PHP release older than the fixed versions is vulnerable to **unauthenticated remote code execution** through crafted web requests. Lightsail does not update running instances, and Bitnami-packaged blueprints bundle their own PHP copy that the system package manager never updates, so the instance stays exploitable until it is patched by hand or recreated from an updated blueprint.",
|
||||
"RelatedUrl": "",
|
||||
"AdditionalURLs": [
|
||||
"https://docs.aws.amazon.com/lightsail/latest/userguide/amazon-lightsail-update-php-security-vulnerabilities.html",
|
||||
"https://docs.aws.amazon.com/lightsail/latest/userguide/migrate-from-bitnami-to-lightsail-blueprints.html",
|
||||
"https://www.php.net/supported-versions.php"
|
||||
],
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "sudo apt update && sudo apt upgrade -y && sudo systemctl restart apache2 && php -v",
|
||||
"NativeIaC": "",
|
||||
"Other": "1. Create a snapshot of the instance\n2. Connect over SSH and check the PHP release: php -v on Lightsail-packaged blueprints, /opt/bitnami/php/bin/php -v on Bitnami-packaged blueprints\n3. Lightsail-packaged blueprints (WordPress, WordPress Multisite, LAMP, Nginx): run sudo apt update && sudo apt upgrade, then restart Apache (sudo systemctl restart apache2) or PHP-FPM on Nginx (sudo systemctl restart php8.2-fpm)\n4. Bitnami-packaged WordPress, WordPress Multisite and LAMP: create a new instance from the matching Lightsail-packaged blueprint, move the content, database and static IP, then delete the old instance\n5. Bitnami-packaged applications without a Lightsail-packaged equivalent (Drupal, Joomla, Magento, PrestaShop and others): install a package-managed PHP-FPM alongside the Bitnami copy and point the Bitnami web server at it, following the AWS guide linked above\n6. Confirm the web server uses the fixed release: 8.2.35, 8.3.36, 8.4.27, 8.5.12 or newer",
|
||||
"Terraform": ""
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Verify the PHP release on every instance created from a PHP blueprint and update it immediately. Prefer **Lightsail-packaged blueprints**, whose PHP is maintained through the system package manager, over Bitnami-packaged ones; where no Lightsail-packaged equivalent exists, replace the bundled Bitnami PHP with a package-managed PHP-FPM so future updates arrive through apt. Upgrade instances still on PHP 8.1 or older, since those branches receive no security fixes.",
|
||||
"Url": "https://hub.prowler.com/check/lightsail_instance_php_blueprint_updated"
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"vulnerabilities"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [
|
||||
"lightsail_instance_automated_snapshots"
|
||||
],
|
||||
"Notes": "This check always reports MANUAL for instances matching a PHP blueprint prefix, because the PHP version is only observable from inside the instance. Instances created from other blueprints are not evaluated."
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
from prowler.lib.check.models import Check, Check_Report_AWS
|
||||
from prowler.providers.aws.services.lightsail.lightsail_client import lightsail_client
|
||||
|
||||
default_php_blueprints = [
|
||||
"wordpress",
|
||||
"lamp",
|
||||
"nginx",
|
||||
"drupal",
|
||||
"joomla",
|
||||
"magento",
|
||||
"prestashop",
|
||||
"plesk",
|
||||
"cpanel",
|
||||
]
|
||||
|
||||
|
||||
class lightsail_instance_php_blueprint_updated(Check):
|
||||
def execute(self):
|
||||
findings = []
|
||||
php_blueprints = (
|
||||
lightsail_client.audit_config.get("lightsail_php_blueprints")
|
||||
or default_php_blueprints
|
||||
)
|
||||
|
||||
for instance in lightsail_client.instances.values():
|
||||
blueprint = instance.blueprint_id.lower()
|
||||
if not any(blueprint.startswith(prefix) for prefix in php_blueprints):
|
||||
continue
|
||||
|
||||
report = Check_Report_AWS(metadata=self.metadata(), resource=instance)
|
||||
report.status = "MANUAL"
|
||||
report.status_extended = f"Lightsail instance {instance.name} was created from the PHP blueprint '{instance.blueprint_id}'; the PHP runtime cannot be inspected through the API, verify over SSH that it runs a fixed release (8.2.35, 8.3.36, 8.4.27, 8.5.12 or newer); Bitnami-packaged blueprints bundle a PHP copy that apt never updates, so migrate them to the Lightsail-packaged blueprint or replace the bundled PHP with a package-managed one."
|
||||
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
@@ -81,6 +81,8 @@ class Lightsail(AWSService):
|
||||
ip_address_type=instance.get("ipAddressType", "ipv4"),
|
||||
ports=ports,
|
||||
auto_snapshot=auto_snapshot_enabled,
|
||||
blueprint_id=instance.get("blueprintId", ""),
|
||||
blueprint_name=instance.get("blueprintName", ""),
|
||||
)
|
||||
|
||||
except Exception as error:
|
||||
@@ -183,6 +185,8 @@ class Instance(BaseModel):
|
||||
ip_address_type: str
|
||||
ports: List[PortRange]
|
||||
auto_snapshot: bool
|
||||
blueprint_id: str = ""
|
||||
blueprint_name: str = ""
|
||||
|
||||
|
||||
class Database(BaseModel):
|
||||
|
||||
@@ -133,11 +133,23 @@ class WAFv2(AWSService):
|
||||
try:
|
||||
rules = get_web_acl.get("WebACL", {}).get("Rules", [])
|
||||
for rule in rules:
|
||||
managed_statement = rule.get("Statement", {}).get(
|
||||
"ManagedRuleGroupStatement"
|
||||
)
|
||||
new_rule = Rule(
|
||||
name=rule.get("Name", ""),
|
||||
cloudwatch_metrics_enabled=rule.get(
|
||||
"VisibilityConfig", {}
|
||||
).get("CloudWatchMetricsEnabled", False),
|
||||
managed_rule_group=(
|
||||
ManagedRuleGroup(
|
||||
vendor_name=managed_statement.get("VendorName", ""),
|
||||
name=managed_statement.get("Name", ""),
|
||||
version=managed_statement.get("Version", ""),
|
||||
)
|
||||
if managed_statement
|
||||
else None
|
||||
),
|
||||
)
|
||||
if (
|
||||
rule.get("Statement", {})
|
||||
@@ -200,11 +212,20 @@ class Scope(Enum):
|
||||
CLOUDFRONT = "CLOUDFRONT"
|
||||
|
||||
|
||||
class ManagedRuleGroup(BaseModel):
|
||||
"""Managed rule group referenced by a Web ACL rule."""
|
||||
|
||||
vendor_name: str
|
||||
name: str
|
||||
version: str = ""
|
||||
|
||||
|
||||
class Rule(BaseModel):
|
||||
"""Model representing a rule for the Web ACL."""
|
||||
|
||||
name: str
|
||||
cloudwatch_metrics_enabled: bool = False
|
||||
managed_rule_group: Optional[ManagedRuleGroup] = None
|
||||
|
||||
|
||||
class WebAclv2(BaseModel):
|
||||
|
||||
Whitespace-only changes.
+44
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"Provider": "aws",
|
||||
"CheckID": "wafv2_webacl_managed_rule_groups_enabled",
|
||||
"CheckTitle": "WAFv2 Web ACL uses managed rule groups",
|
||||
"CheckType": [
|
||||
"Software and Configuration Checks/AWS Security Best Practices",
|
||||
"Effects/Data Exposure"
|
||||
],
|
||||
"ServiceName": "wafv2",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "arn:partition:wafv2:region:account-id:scope/webacl/webacl-name/webacl-id",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "AwsWafv2WebAcl",
|
||||
"ResourceGroup": "network",
|
||||
"Description": "**WAFv2 Web ACLs** are evaluated to confirm at least one rule references a **managed rule group** (`ManagedRuleGroupStatement`), such as the AWS Core rule set or the PHP application rule set, whose signatures the vendor updates when new attack patterns appear.",
|
||||
"Risk": "A Web ACL built only from custom rules receives no vendor-maintained signatures, so newly disclosed exploitation patterns against the protected application reach the backend uninspected until someone writes a rule by hand. Managed rule groups are the compensating control cloud providers ship while runtime patches roll out, including request body size limits and runtime-specific rules.",
|
||||
"RelatedUrl": "",
|
||||
"AdditionalURLs": [
|
||||
"https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups.html",
|
||||
"https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-use-case.html#aws-managed-rule-groups-use-case-php-app",
|
||||
"https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-baseline.html"
|
||||
],
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "aws wafv2 get-web-acl --name <webacl-name> --scope <REGIONAL|CLOUDFRONT> --id <webacl-id> > webacl.json && <append {\"Name\":\"AWS-AWSManagedRulesCommonRuleSet\",\"Priority\":<unused-priority>,\"Statement\":{\"ManagedRuleGroupStatement\":{\"VendorName\":\"AWS\",\"Name\":\"AWSManagedRulesCommonRuleSet\"}},\"OverrideAction\":{\"None\":{}},\"VisibilityConfig\":{\"SampledRequestsEnabled\":true,\"CloudWatchMetricsEnabled\":true,\"MetricName\":\"AWSManagedRulesCommonRuleSet\"}} to .WebACL.Rules in webacl.json> && aws wafv2 update-web-acl --name <webacl-name> --scope <REGIONAL|CLOUDFRONT> --id <webacl-id> --lock-token <LockToken from webacl.json> --default-action <DefaultAction from webacl.json> --visibility-config <VisibilityConfig from webacl.json> --rules <Rules from webacl.json including the new rule>",
|
||||
"NativeIaC": "```yaml\nResources:\n <example_resource_name>:\n Type: AWS::WAFv2::WebACL\n Properties:\n Name: <example_resource_name>\n Scope: REGIONAL\n DefaultAction:\n Allow: {}\n VisibilityConfig:\n SampledRequestsEnabled: true\n CloudWatchMetricsEnabled: true\n MetricName: <example_resource_name>\n Rules:\n - Name: AWS-AWSManagedRulesCommonRuleSet\n Priority: 0\n OverrideAction:\n None: {}\n Statement:\n ManagedRuleGroupStatement: # Critical: managed rule group to pass the check\n VendorName: AWS\n Name: AWSManagedRulesCommonRuleSet\n VisibilityConfig:\n SampledRequestsEnabled: true\n CloudWatchMetricsEnabled: true\n MetricName: AWSManagedRulesCommonRuleSet\n```",
|
||||
"Other": "1. Open the AWS WAF console and select the Web ACL\n2. Open the Rules tab and choose Add rules > Add managed rule groups\n3. Enable the AWS Core rule set and the rule groups matching the protected application (for example PHP application); the console keeps the existing rules and default action\n4. Save the Web ACL and review sampled requests before switching rules from Count to Block",
|
||||
"Terraform": "```hcl\nresource \"aws_wafv2_web_acl\" \"<example_resource_name>\" {\n name = \"<example_resource_name>\"\n scope = \"REGIONAL\"\n\n default_action {\n allow {}\n }\n\n rule {\n name = \"AWS-AWSManagedRulesCommonRuleSet\"\n priority = 0\n\n override_action {\n none {}\n }\n\n statement {\n managed_rule_group_statement { # Critical: managed rule group to pass the check\n name = \"AWSManagedRulesCommonRuleSet\"\n vendor_name = \"AWS\"\n }\n }\n\n visibility_config {\n cloudwatch_metrics_enabled = true\n metric_name = \"AWSManagedRulesCommonRuleSet\"\n sampled_requests_enabled = true\n }\n }\n\n visibility_config {\n cloudwatch_metrics_enabled = true\n metric_name = \"<example_resource_name>\"\n sampled_requests_enabled = true\n }\n}\n```"
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Attach the **AWS Core rule set** to every Web ACL and add the managed rule groups that match the protected stack (PHP, SQL database, known bad inputs). Keep the default rule group version so vendor updates apply automatically, and review sampled requests before moving rules from Count to Block.",
|
||||
"Url": "https://hub.prowler.com/check/wafv2_webacl_managed_rule_groups_enabled"
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"internet-exposed"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [
|
||||
"wafv2_webacl_with_rules",
|
||||
"wafv2_webacl_logging_enabled"
|
||||
],
|
||||
"Notes": "Rule groups attached through Firewall Manager are not inspected for managed statements, so a Web ACL protected only by Firewall Manager policies may be reported as failing. UpdateWebACL replaces the whole rule list and default action, so any CLI or IaC remediation must resend the existing configuration together with the new managed rule group."
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
from prowler.lib.check.models import Check, Check_Report_AWS
|
||||
from prowler.providers.aws.services.wafv2.wafv2_client import wafv2_client
|
||||
|
||||
|
||||
class wafv2_webacl_managed_rule_groups_enabled(Check):
|
||||
def execute(self):
|
||||
findings = []
|
||||
for web_acl in wafv2_client.web_acls.values():
|
||||
report = Check_Report_AWS(metadata=self.metadata(), resource=web_acl)
|
||||
managed_rule_groups = [
|
||||
f"{rule.managed_rule_group.vendor_name}/{rule.managed_rule_group.name}"
|
||||
for rule in web_acl.rules
|
||||
if rule.managed_rule_group
|
||||
]
|
||||
report.status = "FAIL"
|
||||
report.status_extended = (
|
||||
f"AWS WAFv2 Web ACL {web_acl.name} does not use any managed rule group."
|
||||
)
|
||||
|
||||
if managed_rule_groups:
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"AWS WAFv2 Web ACL {web_acl.name} uses managed rule groups: {', '.join(managed_rule_groups)}."
|
||||
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
Whitespace-only changes.
@@ -0,0 +1,32 @@
|
||||
import re
|
||||
|
||||
_VERSION_RE = re.compile(r"\d+(?:\.\d+)*")
|
||||
_JAVA_SUFFIX_RE = re.compile(r"-(?:java|jre)(\d+)", re.IGNORECASE)
|
||||
|
||||
|
||||
def parse_runtime_version(value) -> tuple[int, ...]:
|
||||
"""Return the first dotted number in a stack string as an int tuple, empty if none."""
|
||||
match = _VERSION_RE.search("" if value is None else str(value))
|
||||
return tuple(int(part) for part in match.group().split(".")) if match else ()
|
||||
|
||||
|
||||
def parse_java_version(value) -> tuple[int, ...]:
|
||||
"""Java major from 'JAVA|17-java17', 'TOMCAT|10.0-java11', '1.8' or '17'."""
|
||||
match = _JAVA_SUFFIX_RE.search("" if value is None else str(value))
|
||||
if match:
|
||||
return (int(match.group(1)),)
|
||||
version = parse_runtime_version(value)
|
||||
# Legacy '1.8' naming for Java 8
|
||||
if len(version) > 1 and version[0] == 1:
|
||||
return (version[1],)
|
||||
return version[:1]
|
||||
|
||||
|
||||
def runtime_version_at_least(
|
||||
current: tuple[int, ...], minimum: tuple[int, ...]
|
||||
) -> bool:
|
||||
"""Compare only the components the platform exposes (no patch level in linuxFxVersion)."""
|
||||
if not current or not minimum:
|
||||
return False
|
||||
length = min(len(current), len(minimum))
|
||||
return current[:length] >= minimum[:length]
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"Provider": "azure",
|
||||
"CheckID": "app_ensure_java_version_is_latest",
|
||||
"CheckTitle": "App Service web app uses the latest supported Java version or 17 by default",
|
||||
"CheckTitle": "App Service web app runs a supported Java version (17 or newer by default)",
|
||||
"CheckType": [],
|
||||
"ServiceName": "app",
|
||||
"SubServiceName": "",
|
||||
@@ -9,7 +9,7 @@
|
||||
"Severity": "low",
|
||||
"ResourceType": "microsoft.web/sites",
|
||||
"ResourceGroup": "serverless",
|
||||
"Description": "**Azure App Service web apps** that run **Java** are assessed to ensure their configured runtime uses the **latest supported major version** (LTS) for the environment, across Linux and Windows.\n\n*Only apps with Java enabled are considered.*",
|
||||
"Description": "**Azure App Service web apps** that run **Java** are assessed to ensure their configured runtime major version is at or above the configured **minimum supported LTS** (`java_latest_version`, `17` by default), across Linux and Windows.\n\n*Only apps with Java enabled are considered.*",
|
||||
"Risk": "Using an **outdated Java runtime** enables known exploits like **remote code execution**, unsafe **deserialization**, and **cryptographic flaws**, risking data theft and tampering (**confidentiality, integrity**) and outages or takeover (**availability**). Unsupported versions also delay critical security patches.",
|
||||
"RelatedUrl": "",
|
||||
"AdditionalURLs": [
|
||||
|
||||
+28
-18
@@ -1,4 +1,8 @@
|
||||
from prowler.lib.check.models import Check, Check_Report_Azure
|
||||
from prowler.providers.azure.lib.runtime.runtime_version import (
|
||||
parse_java_version,
|
||||
runtime_version_at_least,
|
||||
)
|
||||
from prowler.providers.azure.services.app.app_client import app_client
|
||||
|
||||
|
||||
@@ -14,27 +18,33 @@ class app_ensure_java_version_is_latest(Check):
|
||||
subscription_id, subscription_id
|
||||
)
|
||||
for app in apps.values():
|
||||
linux_framework = getattr(app.configurations, "linux_fx_version", "")
|
||||
windows_framework_version = getattr(
|
||||
app.configurations, "java_version", None
|
||||
# The SDK can return None for linuxFxVersion, which getattr preserves
|
||||
linux_framework = (
|
||||
getattr(app.configurations, "linux_fx_version", "") or ""
|
||||
)
|
||||
windows_version = getattr(app.configurations, "java_version", None)
|
||||
|
||||
if "java" in linux_framework.lower() or windows_framework_version:
|
||||
report = Check_Report_Azure(metadata=self.metadata(), resource=app)
|
||||
report.subscription = subscription_id
|
||||
report.status = "FAIL"
|
||||
java_latest_version = app_client.audit_config.get(
|
||||
"java_latest_version", "17"
|
||||
)
|
||||
report.status_extended = f"Java version is set to '{f'java{windows_framework_version}' if windows_framework_version else linux_framework}', but should be set to 'java {java_latest_version}' for app '{app.name}' in subscription '{subscription_name} ({subscription_id})'."
|
||||
if "java" not in linux_framework.lower() and not windows_version:
|
||||
continue
|
||||
|
||||
if (
|
||||
f"java{java_latest_version}" in linux_framework
|
||||
or java_latest_version == windows_framework_version
|
||||
):
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"Java version is set to 'java {java_latest_version}' for app '{app.name}' in subscription '{subscription_name} ({subscription_id})'."
|
||||
framework = (
|
||||
linux_framework
|
||||
if "java" in linux_framework.lower()
|
||||
else f"java{windows_version}"
|
||||
)
|
||||
minimum = app_client.audit_config.get("java_latest_version") or "17"
|
||||
|
||||
findings.append(report)
|
||||
report = Check_Report_Azure(metadata=self.metadata(), resource=app)
|
||||
report.subscription = subscription_id
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"Java version is set to '{framework}', which is below the minimum supported version 'java {minimum}', for app '{app.name}' in subscription '{subscription_name} ({subscription_id})'."
|
||||
|
||||
if runtime_version_at_least(
|
||||
parse_java_version(framework), parse_java_version(minimum)
|
||||
):
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"Java version is set to '{framework}', which meets the minimum supported version 'java {minimum}', for app '{app.name}' in subscription '{subscription_name} ({subscription_id})'."
|
||||
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"Provider": "azure",
|
||||
"CheckID": "app_ensure_php_version_is_latest",
|
||||
"CheckTitle": "App Service web app uses the latest supported PHP version or 8.5 by default",
|
||||
"CheckTitle": "App Service web app runs a supported PHP version (8.5 or newer by default)",
|
||||
"CheckType": [],
|
||||
"ServiceName": "app",
|
||||
"SubServiceName": "",
|
||||
@@ -9,7 +9,7 @@
|
||||
"Severity": "low",
|
||||
"ResourceType": "microsoft.web/sites",
|
||||
"ResourceGroup": "serverless",
|
||||
"Description": "**Azure App Service web apps** running **PHP** are evaluated to ensure the runtime is configured to the **latest supported** release. The finding compares the app's PHP stack (from `linuxFxVersion` or `php_version`) with the newest available version.",
|
||||
"Description": "**Azure App Service web apps** running **PHP** are evaluated to ensure the runtime is at or above the configured **minimum supported** release. The finding compares the app's PHP stack (from `linuxFxVersion` or `php_version`) with `php_latest_version` (`8.5` by default); any version at or above it passes.",
|
||||
"Risk": "Using **outdated PHP** enables exploitation of known flaws, including **remote code execution**, causing secret disclosure (confidentiality), unauthorized changes (integrity), and crashes or downtime (availability). Deprecated versions lack patches, widening exposure and instability.",
|
||||
"RelatedUrl": "",
|
||||
"AdditionalURLs": [
|
||||
@@ -34,5 +34,5 @@
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": "If your app is written using version-dependent features or libraries, they may not be available on the latest version. If you wish to upgrade, research the impact thoroughly. Upgrading may have unforeseen consequences that could result in downtime"
|
||||
"Notes": "App Service exposes only the PHP major.minor version, not the patch level, so this check cannot confirm a specific patch release is deployed. If your app depends on version-specific features or libraries, research the impact before upgrading."
|
||||
}
|
||||
+29
-21
@@ -1,4 +1,8 @@
|
||||
from prowler.lib.check.models import Check, Check_Report_Azure
|
||||
from prowler.providers.azure.lib.runtime.runtime_version import (
|
||||
parse_runtime_version,
|
||||
runtime_version_at_least,
|
||||
)
|
||||
from prowler.providers.azure.services.app.app_client import app_client
|
||||
|
||||
|
||||
@@ -14,29 +18,33 @@ class app_ensure_php_version_is_latest(Check):
|
||||
subscription_id, subscription_id
|
||||
)
|
||||
for app in apps.values():
|
||||
framework = getattr(app.configurations, "linux_fx_version", "")
|
||||
# The SDK can return None for linuxFxVersion, which getattr preserves
|
||||
linux_framework = (
|
||||
getattr(app.configurations, "linux_fx_version", "") or ""
|
||||
)
|
||||
windows_version = getattr(app.configurations, "php_version", "")
|
||||
|
||||
if "php" in framework.lower() or getattr(
|
||||
app.configurations, "php_version", ""
|
||||
if "php" not in linux_framework.lower() and not windows_version:
|
||||
continue
|
||||
|
||||
framework = (
|
||||
linux_framework
|
||||
if "php" in linux_framework.lower()
|
||||
else windows_version
|
||||
)
|
||||
minimum = app_client.audit_config.get("php_latest_version") or "8.5"
|
||||
|
||||
report = Check_Report_Azure(metadata=self.metadata(), resource=app)
|
||||
report.subscription = subscription_id
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"PHP version is set to '{framework}', which is below the minimum supported version '{minimum}', for app '{app.name}' in subscription '{subscription_name} ({subscription_id})'."
|
||||
|
||||
if runtime_version_at_least(
|
||||
parse_runtime_version(framework), parse_runtime_version(minimum)
|
||||
):
|
||||
report = Check_Report_Azure(metadata=self.metadata(), resource=app)
|
||||
report.subscription = subscription_id
|
||||
report.status = "FAIL"
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"PHP version is set to '{framework}', which meets the minimum supported version '{minimum}', for app '{app.name}' in subscription '{subscription_name} ({subscription_id})'."
|
||||
|
||||
php_latest_version = app_client.audit_config.get(
|
||||
"php_latest_version", "8.5"
|
||||
)
|
||||
|
||||
report.status_extended = f"PHP version is set to '{framework}', the latest version that you could use is the '{php_latest_version}' version, for app '{app.name}' in subscription '{subscription_name} ({subscription_id})'."
|
||||
|
||||
if (
|
||||
php_latest_version in framework
|
||||
or getattr(app.configurations, "php_version", None)
|
||||
== php_latest_version
|
||||
):
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"PHP version is set to '{php_latest_version}' for app '{app.name}' in subscription '{subscription_name} ({subscription_id})'."
|
||||
|
||||
findings.append(report)
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"Provider": "azure",
|
||||
"CheckID": "app_ensure_python_version_is_latest",
|
||||
"CheckTitle": "App Service web app uses the latest supported Python version or 3.12 by default",
|
||||
"CheckTitle": "App Service web app runs a supported Python version (3.12 or newer by default)",
|
||||
"CheckType": [],
|
||||
"ServiceName": "app",
|
||||
"SubServiceName": "",
|
||||
@@ -9,7 +9,7 @@
|
||||
"Severity": "low",
|
||||
"ResourceType": "microsoft.web/sites",
|
||||
"ResourceGroup": "serverless",
|
||||
"Description": "**Azure App Service web apps** using **Python** are assessed to confirm the runtime is the **latest supported version** (e.g., `3.12`). The evaluation reads the app's stack configuration to detect Python usage and compares the configured runtime against the defined latest baseline.",
|
||||
"Description": "**Azure App Service web apps** using **Python** are assessed to confirm the runtime is at or above the configured **minimum supported version** (`python_latest_version`, `3.12` by default). The evaluation reads the app's stack configuration to detect Python usage and compares the configured runtime against that baseline.",
|
||||
"Risk": "Outdated **Python runtimes** weaken security and reliability:\n- Compromise confidentiality via known interpreter/SSL flaws\n- Undermine integrity through RCE and package exploitation\n- Reduce availability when deprecated versions lose patches and break under load",
|
||||
"RelatedUrl": "",
|
||||
"AdditionalURLs": [
|
||||
|
||||
+29
-19
@@ -1,4 +1,8 @@
|
||||
from prowler.lib.check.models import Check, Check_Report_Azure
|
||||
from prowler.providers.azure.lib.runtime.runtime_version import (
|
||||
parse_runtime_version,
|
||||
runtime_version_at_least,
|
||||
)
|
||||
from prowler.providers.azure.services.app.app_client import app_client
|
||||
|
||||
|
||||
@@ -14,27 +18,33 @@ class app_ensure_python_version_is_latest(Check):
|
||||
subscription_id, subscription_id
|
||||
)
|
||||
for app in apps.values():
|
||||
framework = getattr(app.configurations, "linux_fx_version", "")
|
||||
# The SDK can return None for linuxFxVersion, which getattr preserves
|
||||
linux_framework = (
|
||||
getattr(app.configurations, "linux_fx_version", "") or ""
|
||||
)
|
||||
windows_version = getattr(app.configurations, "python_version", "")
|
||||
|
||||
if "python" in framework.lower() or getattr(
|
||||
app.configurations, "python_version", ""
|
||||
if "python" not in linux_framework.lower() and not windows_version:
|
||||
continue
|
||||
|
||||
framework = (
|
||||
linux_framework
|
||||
if "python" in linux_framework.lower()
|
||||
else windows_version
|
||||
)
|
||||
minimum = app_client.audit_config.get("python_latest_version") or "3.12"
|
||||
|
||||
report = Check_Report_Azure(metadata=self.metadata(), resource=app)
|
||||
report.subscription = subscription_id
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"Python version is set to '{framework}', which is below the minimum supported version '{minimum}', for app '{app.name}' in subscription '{subscription_name} ({subscription_id})'."
|
||||
|
||||
if runtime_version_at_least(
|
||||
parse_runtime_version(framework), parse_runtime_version(minimum)
|
||||
):
|
||||
report = Check_Report_Azure(metadata=self.metadata(), resource=app)
|
||||
report.subscription = subscription_id
|
||||
report.status = "FAIL"
|
||||
python_latest_version = app_client.audit_config.get(
|
||||
"python_latest_version", "3.12"
|
||||
)
|
||||
report.status_extended = f"Python version is '{framework}', the latest version that you could use is the '{python_latest_version}' version, for app '{app.name}' in subscription '{subscription_name} ({subscription_id})'."
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"Python version is set to '{framework}', which meets the minimum supported version '{minimum}', for app '{app.name}' in subscription '{subscription_name} ({subscription_id})'."
|
||||
|
||||
if (
|
||||
python_latest_version in framework
|
||||
or getattr(app.configurations, "python_version", None)
|
||||
== python_latest_version
|
||||
):
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"Python version is set to '{python_latest_version}' for app '{app.name}' in subscription '{subscription_name} ({subscription_id})'."
|
||||
|
||||
findings.append(report)
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
Whitespace-only changes.
@@ -0,0 +1,4 @@
|
||||
from prowler.providers.common.provider import Provider
|
||||
from prowler.providers.gcp.services.appengine.appengine_service import AppEngine
|
||||
|
||||
appengine_client = AppEngine(Provider.get_global_provider())
|
||||
@@ -0,0 +1,109 @@
|
||||
from googleapiclient.errors import HttpError
|
||||
from pydantic.v1 import BaseModel
|
||||
|
||||
from prowler.lib.logger import logger
|
||||
from prowler.providers.gcp.config import DEFAULT_RETRY_ATTEMPTS
|
||||
from prowler.providers.gcp.gcp_provider import GcpProvider
|
||||
from prowler.providers.gcp.lib.service.service import GCPService
|
||||
|
||||
|
||||
class AppEngine(GCPService):
|
||||
"""App Engine Admin API client exposing every service version per project."""
|
||||
|
||||
def __init__(self, provider: GcpProvider) -> None:
|
||||
super().__init__("appengine", provider)
|
||||
self.versions = []
|
||||
self._get_versions()
|
||||
|
||||
def _get_versions(self) -> None:
|
||||
for project_id in self.project_ids:
|
||||
try:
|
||||
app = (
|
||||
self.client.apps()
|
||||
.get(appsId=project_id)
|
||||
.execute(num_retries=DEFAULT_RETRY_ATTEMPTS)
|
||||
)
|
||||
except HttpError as http_error:
|
||||
# 404 means the API is enabled but no App Engine application exists
|
||||
if http_error.resp.status == 404:
|
||||
logger.warning(
|
||||
f"{project_id} -- App Engine API is enabled but no application exists"
|
||||
)
|
||||
else:
|
||||
logger.error(
|
||||
f"{project_id} -- {http_error.__class__.__name__}[{http_error.__traceback__.tb_lineno}]: {http_error}"
|
||||
)
|
||||
continue
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{project_id} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
continue
|
||||
|
||||
location = app.get("locationId", "")
|
||||
try:
|
||||
services = self.client.apps().services()
|
||||
services_request = services.list(appsId=project_id)
|
||||
while services_request is not None:
|
||||
services_response = services_request.execute(
|
||||
num_retries=DEFAULT_RETRY_ATTEMPTS
|
||||
)
|
||||
for service in services_response.get("services", []):
|
||||
service_id = service.get("id", "")
|
||||
self._get_service_versions(project_id, location, service_id)
|
||||
services_request = services.list_next(
|
||||
previous_request=services_request,
|
||||
previous_response=services_response,
|
||||
)
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{project_id} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
|
||||
def _get_service_versions(
|
||||
self, project_id: str, location: str, service_id: str
|
||||
) -> None:
|
||||
try:
|
||||
versions = self.client.apps().services().versions()
|
||||
request = versions.list(appsId=project_id, servicesId=service_id)
|
||||
while request is not None:
|
||||
response = request.execute(num_retries=DEFAULT_RETRY_ATTEMPTS)
|
||||
for version in response.get("versions", []):
|
||||
version_id = version.get("id", "")
|
||||
self.versions.append(
|
||||
Version(
|
||||
id=version.get(
|
||||
"name",
|
||||
f"apps/{project_id}/services/{service_id}/versions/{version_id}",
|
||||
),
|
||||
name=f"{service_id}/{version_id}",
|
||||
version_id=version_id,
|
||||
service=service_id,
|
||||
project_id=project_id,
|
||||
location=location,
|
||||
runtime=version.get("runtime", ""),
|
||||
env=version.get("env", "standard"),
|
||||
serving_status=version.get("servingStatus", "SERVING"),
|
||||
)
|
||||
)
|
||||
request = versions.list_next(
|
||||
previous_request=request, previous_response=response
|
||||
)
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{project_id} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
|
||||
|
||||
class Version(BaseModel):
|
||||
"""App Engine service version consumed by GCP checks."""
|
||||
|
||||
id: str
|
||||
name: str
|
||||
version_id: str
|
||||
service: str
|
||||
project_id: str
|
||||
location: str
|
||||
runtime: str
|
||||
env: str = "standard"
|
||||
serving_status: str = "SERVING"
|
||||
Whitespace-only changes.
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"Provider": "gcp",
|
||||
"CheckID": "appengine_version_php_runtime_supported",
|
||||
"CheckTitle": "App Engine serving version runs a supported PHP runtime",
|
||||
"CheckType": [],
|
||||
"ServiceName": "appengine",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "critical",
|
||||
"ResourceType": "appengine.googleapis.com/Version",
|
||||
"Description": "**App Engine** serving versions that run **PHP** are evaluated to confirm the runtime is a supported branch (`appengine_php_min_version`, `8.2` by default). Standard environment runtimes such as `php82` are compared directly; flexible environment versions are reported for manual verification because their PHP binary lives in a customer-built image that is not patched in place.",
|
||||
"Risk": "PHP runtimes older than the supported branches are vulnerable to **unauthenticated remote code execution** through crafted web requests and receive no fixes, so a serving version pinned to one of them stays exploitable. Flexible environment versions keep the vulnerable binary until they are redeployed on a fixed release.",
|
||||
"RelatedUrl": "",
|
||||
"AdditionalURLs": [
|
||||
"https://cloud.google.com/appengine/docs/standard/lifecycle/support-schedule",
|
||||
"https://cloud.google.com/appengine/docs/standard/php-gen2/runtime",
|
||||
"https://cloud.google.com/appengine/docs/flexible/php/runtime",
|
||||
"https://www.php.net/supported-versions.php"
|
||||
],
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "gcloud app deploy app.yaml --project <PROJECT_ID> # with runtime: php84 in app.yaml",
|
||||
"NativeIaC": "",
|
||||
"Other": "1. Edit the service's app.yaml and set runtime to a supported PHP branch (for example php84)\n2. For flexible environment services, rebuild the image on a fixed PHP release\n3. Run gcloud app deploy to create a new version\n4. Route traffic to the new version and stop or delete versions still on unsupported runtimes",
|
||||
"Terraform": "```hcl\nresource \"google_app_engine_standard_app_version\" \"<example_resource_name>\" {\n version_id = \"v2\"\n service = \"default\"\n runtime = \"php84\" # Critical: supported PHP runtime to pass the check\n\n entrypoint {\n shell = \"serve index.php\"\n }\n\n deployment {\n zip {\n source_url = \"https://storage.googleapis.com/<example_resource_id>/app.zip\"\n }\n }\n}\n```"
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Deploy every App Engine service on a **supported PHP runtime** and redeploy whenever a branch approaches end of support. Standard environment runtimes on supported branches receive security fixes automatically; flexible environment images must be rebuilt and redeployed, so prefer the standard environment for PHP workloads where possible.",
|
||||
"Url": "https://hub.prowler.com/check/appengine_version_php_runtime_supported"
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"vulnerabilities"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": "Only versions with serving status SERVING and a PHP runtime are evaluated. Flexible environment versions are reported as MANUAL since the deployed PHP release is not visible through the Admin API; versions on the custom runtime are not evaluated at all because the Admin API does not expose which language they run."
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
import re
|
||||
|
||||
from prowler.lib.check.models import Check, Check_Report_GCP
|
||||
from prowler.providers.gcp.services.appengine.appengine_client import appengine_client
|
||||
|
||||
# Standard environment runtimes look like "php82"; flexible ones are just "php"
|
||||
PHP_RUNTIME_RE = re.compile(r"^php(?P<major>\d)(?P<minor>\d+)?$", re.IGNORECASE)
|
||||
|
||||
|
||||
def _version_tuple(value: str) -> tuple[int, ...]:
|
||||
return tuple(int(part) for part in value.split(".")) if value else ()
|
||||
|
||||
|
||||
class appengine_version_php_runtime_supported(Check):
|
||||
"""Check that serving App Engine versions run a supported PHP runtime."""
|
||||
|
||||
def execute(self) -> list[Check_Report_GCP]:
|
||||
findings = []
|
||||
# `or` covers an explicit null in the config file, which .get() would return as None
|
||||
minimum = (
|
||||
appengine_client.audit_config.get("appengine_php_min_version") or "8.2"
|
||||
)
|
||||
|
||||
for version in appengine_client.versions:
|
||||
runtime = version.runtime.lower()
|
||||
if not runtime.startswith("php") or version.serving_status != "SERVING":
|
||||
continue
|
||||
|
||||
report = Check_Report_GCP(
|
||||
metadata=self.metadata(),
|
||||
resource=version,
|
||||
resource_id=version.id,
|
||||
)
|
||||
match = PHP_RUNTIME_RE.match(runtime)
|
||||
|
||||
if version.env.lower() in ("flex", "flexible") or not (
|
||||
match and match["minor"]
|
||||
):
|
||||
report.status = "MANUAL"
|
||||
report.status_extended = f"App Engine version {version.name} runs on the flexible environment with runtime '{version.runtime}', which is not patched in place; redeploy it on a fixed PHP release ({minimum} or newer) and verify the image."
|
||||
else:
|
||||
php_version = f"{match['major']}.{match['minor']}"
|
||||
if _version_tuple(php_version) < _version_tuple(minimum):
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"App Engine version {version.name} runs PHP {php_version} ('{version.runtime}'), a runtime that no longer receives security fixes (minimum supported version is {minimum})."
|
||||
else:
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"App Engine version {version.name} runs PHP {php_version} ('{version.runtime}'), a supported runtime that receives security fixes automatically."
|
||||
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
@@ -111,6 +111,19 @@ config_aws = {
|
||||
"max_idle_disconnect_timeout_in_seconds": 600,
|
||||
"max_disconnect_timeout_in_seconds": 300,
|
||||
"max_session_duration_seconds": 36000,
|
||||
"elasticbeanstalk_php_min_branch": "8.2",
|
||||
"elasticbeanstalk_php_min_platform_version": "4.13.13",
|
||||
"lightsail_php_blueprints": [
|
||||
"wordpress",
|
||||
"lamp",
|
||||
"nginx",
|
||||
"drupal",
|
||||
"joomla",
|
||||
"magento",
|
||||
"prestashop",
|
||||
"plesk",
|
||||
"cpanel",
|
||||
],
|
||||
"obsolete_lambda_runtimes": [
|
||||
"java8",
|
||||
"go1.x",
|
||||
|
||||
@@ -93,6 +93,28 @@ aws:
|
||||
# aws.appstream_fleet_maximum_session_duration
|
||||
max_session_duration_seconds: 36000 # 10 Hours
|
||||
|
||||
# AWS Elastic Beanstalk Configuration
|
||||
# aws.elasticbeanstalk_environment_php_platform_updated
|
||||
# Oldest PHP branch still receiving security fixes
|
||||
elasticbeanstalk_php_min_branch: "8.2"
|
||||
# Oldest Amazon Linux 2023 PHP platform version bundling the fixed PHP release
|
||||
elasticbeanstalk_php_min_platform_version: "4.13.13"
|
||||
|
||||
# AWS Lightsail Configuration
|
||||
# aws.lightsail_instance_php_blueprint_updated
|
||||
# Blueprint id prefixes that ship PHP and require manual runtime verification
|
||||
lightsail_php_blueprints:
|
||||
[
|
||||
"wordpress",
|
||||
"lamp",
|
||||
"nginx",
|
||||
"drupal",
|
||||
"joomla",
|
||||
"magento",
|
||||
"prestashop",
|
||||
"plesk",
|
||||
"cpanel",
|
||||
]
|
||||
# AWS Lambda Configuration
|
||||
# aws.awslambda_function_using_supported_runtimes
|
||||
obsolete_lambda_runtimes:
|
||||
@@ -415,6 +437,7 @@ azure:
|
||||
defender_attack_path_minimal_risk_level: "High"
|
||||
|
||||
# Azure App Service
|
||||
# Minimum runtime versions; any newer version passes
|
||||
# azure.app_ensure_php_version_is_latest
|
||||
php_latest_version: "8.2"
|
||||
# azure.app_ensure_python_version_is_latest
|
||||
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
||||
from prowler.providers.aws.services.elasticbeanstalk.elasticbeanstalk_service import (
|
||||
Environment,
|
||||
)
|
||||
from tests.providers.aws.utils import (
|
||||
AWS_ACCOUNT_NUMBER,
|
||||
AWS_REGION_EU_WEST_1,
|
||||
set_mocked_aws_provider,
|
||||
)
|
||||
|
||||
CHECK_PATH = "prowler.providers.aws.services.elasticbeanstalk.elasticbeanstalk_environment_php_platform_updated.elasticbeanstalk_environment_php_platform_updated"
|
||||
ENV_ARN = f"arn:aws:elasticbeanstalk:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:environment/test-app/test-env"
|
||||
|
||||
|
||||
def _environment(solution_stack_name="", platform_arn=""):
|
||||
return Environment(
|
||||
id="e-abc123",
|
||||
name="test-env",
|
||||
arn=ENV_ARN,
|
||||
region=AWS_REGION_EU_WEST_1,
|
||||
application_name="test-app",
|
||||
solution_stack_name=solution_stack_name,
|
||||
platform_arn=platform_arn,
|
||||
)
|
||||
|
||||
|
||||
def _run_check(environments, audit_config=None):
|
||||
elasticbeanstalk_client = mock.MagicMock()
|
||||
elasticbeanstalk_client.environments = environments
|
||||
elasticbeanstalk_client.audit_config = audit_config or {}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_aws_provider([AWS_REGION_EU_WEST_1]),
|
||||
),
|
||||
mock.patch(
|
||||
f"{CHECK_PATH}.elasticbeanstalk_client", new=elasticbeanstalk_client
|
||||
),
|
||||
):
|
||||
from prowler.providers.aws.services.elasticbeanstalk.elasticbeanstalk_environment_php_platform_updated.elasticbeanstalk_environment_php_platform_updated import (
|
||||
elasticbeanstalk_environment_php_platform_updated,
|
||||
)
|
||||
|
||||
return elasticbeanstalk_environment_php_platform_updated().execute()
|
||||
|
||||
|
||||
class Test_elasticbeanstalk_environment_php_platform_updated:
|
||||
def test_no_environments(self):
|
||||
assert len(_run_check({})) == 0
|
||||
|
||||
def test_non_php_environment_is_skipped(self):
|
||||
environments = {
|
||||
ENV_ARN: _environment("64bit Amazon Linux 2023 v4.7.3 running Python 3.12")
|
||||
}
|
||||
assert len(_run_check(environments)) == 0
|
||||
|
||||
def test_environment_without_stack_is_skipped(self):
|
||||
assert len(_run_check({ENV_ARN: _environment()})) == 0
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"solution_stack_name,expected_status,expected_message",
|
||||
[
|
||||
(
|
||||
"64bit Amazon Linux 2023 v4.13.13 running PHP 8.4",
|
||||
"PASS",
|
||||
"Elastic Beanstalk environment test-env runs PHP 8.4 on platform version 4.13.13, which includes the fixed PHP release.",
|
||||
),
|
||||
(
|
||||
"64bit Amazon Linux 2023 v4.14.0 running PHP 8.2",
|
||||
"PASS",
|
||||
"Elastic Beanstalk environment test-env runs PHP 8.2 on platform version 4.14.0, which includes the fixed PHP release.",
|
||||
),
|
||||
(
|
||||
"64bit Amazon Linux 2023 v4.13.12 running PHP 8.5",
|
||||
"FAIL",
|
||||
"Elastic Beanstalk environment test-env runs PHP 8.5 on platform version 4.13.12, below the minimum fixed platform version 4.13.13.",
|
||||
),
|
||||
(
|
||||
"64bit Amazon Linux 2023 v4.9.2 running PHP 8.3",
|
||||
"FAIL",
|
||||
"Elastic Beanstalk environment test-env runs PHP 8.3 on platform version 4.9.2, below the minimum fixed platform version 4.13.13.",
|
||||
),
|
||||
(
|
||||
"64bit Amazon Linux 2023 v4.13.13 running PHP 8.1",
|
||||
"FAIL",
|
||||
"Elastic Beanstalk environment test-env runs PHP 8.1, a branch that no longer receives security fixes (minimum supported branch is 8.2).",
|
||||
),
|
||||
(
|
||||
"64bit Amazon Linux 2 v3.9.5 running PHP 8.2",
|
||||
"FAIL",
|
||||
"Elastic Beanstalk environment test-env runs PHP 8.2 on platform '64bit Amazon Linux 2 v3.9.5 running PHP 8.2', a platform family without fixed PHP releases; migrate it to a PHP 8.2 Amazon Linux 2023 platform version 4.13.13 or newer.",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_php_environment(
|
||||
self, solution_stack_name, expected_status, expected_message
|
||||
):
|
||||
result = _run_check({ENV_ARN: _environment(solution_stack_name)})
|
||||
assert len(result) == 1
|
||||
assert result[0].status == expected_status
|
||||
assert result[0].status_extended == expected_message
|
||||
assert result[0].resource_id == "e-abc123"
|
||||
assert result[0].resource_arn == ENV_ARN
|
||||
assert result[0].region == AWS_REGION_EU_WEST_1
|
||||
|
||||
def test_platform_arn_fallback(self):
|
||||
environments = {
|
||||
ENV_ARN: _environment(
|
||||
platform_arn=f"arn:aws:elasticbeanstalk:{AWS_REGION_EU_WEST_1}::platform/PHP 8.3 running on 64bit Amazon Linux 2023/4.13.13"
|
||||
)
|
||||
}
|
||||
result = _run_check(environments)
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
|
||||
def test_custom_minimums(self):
|
||||
environments = {
|
||||
ENV_ARN: _environment("64bit Amazon Linux 2023 v4.13.13 running PHP 8.3")
|
||||
}
|
||||
result = _run_check(
|
||||
environments,
|
||||
{
|
||||
"elasticbeanstalk_php_min_branch": "8.4",
|
||||
"elasticbeanstalk_php_min_platform_version": "4.13.13",
|
||||
},
|
||||
)
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "Elastic Beanstalk environment test-env runs PHP 8.3, a branch that no longer receives security fixes (minimum supported branch is 8.4)."
|
||||
)
|
||||
@@ -7,7 +7,11 @@ from moto import mock_aws
|
||||
from prowler.providers.aws.services.elasticbeanstalk.elasticbeanstalk_service import (
|
||||
ElasticBeanstalk,
|
||||
)
|
||||
from tests.providers.aws.utils import AWS_REGION_EU_WEST_1, set_mocked_aws_provider
|
||||
from tests.providers.aws.utils import (
|
||||
AWS_ACCOUNT_NUMBER,
|
||||
AWS_REGION_EU_WEST_1,
|
||||
set_mocked_aws_provider,
|
||||
)
|
||||
|
||||
make_api_call = botocore.client.BaseClient._make_api_call
|
||||
|
||||
@@ -120,6 +124,44 @@ class Test_ElasticBeanstalk_Service:
|
||||
== "test-app"
|
||||
)
|
||||
|
||||
@mock_aws
|
||||
def test_describe_environments_platform(self):
|
||||
environment_arn = f"arn:aws:elasticbeanstalk:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:environment/test-app/test-env"
|
||||
|
||||
def mock_describe_environments(self, operation_name, kwarg):
|
||||
if operation_name == "DescribeEnvironments":
|
||||
return {
|
||||
"Environments": [
|
||||
{
|
||||
"EnvironmentId": "e-abc123",
|
||||
"EnvironmentArn": environment_arn,
|
||||
"ApplicationName": "test-app",
|
||||
"EnvironmentName": "test-env",
|
||||
"SolutionStackName": "64bit Amazon Linux 2023 v4.13.13 running PHP 8.4",
|
||||
"PlatformArn": f"arn:aws:elasticbeanstalk:{AWS_REGION_EU_WEST_1}::platform/PHP 8.4 running on 64bit Amazon Linux 2023/4.13.13",
|
||||
}
|
||||
]
|
||||
}
|
||||
return make_api_call(self, operation_name, kwarg)
|
||||
|
||||
with patch(
|
||||
"botocore.client.BaseClient._make_api_call",
|
||||
new=mock_describe_environments,
|
||||
):
|
||||
elasticbeanstalk = ElasticBeanstalk(
|
||||
set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
|
||||
)
|
||||
|
||||
environment = elasticbeanstalk.environments[environment_arn]
|
||||
assert (
|
||||
environment.solution_stack_name
|
||||
== "64bit Amazon Linux 2023 v4.13.13 running PHP 8.4"
|
||||
)
|
||||
assert (
|
||||
environment.platform_arn
|
||||
== f"arn:aws:elasticbeanstalk:{AWS_REGION_EU_WEST_1}::platform/PHP 8.4 running on 64bit Amazon Linux 2023/4.13.13"
|
||||
)
|
||||
|
||||
# Test _describe_configuration_settings
|
||||
@mock_aws
|
||||
def test_describe_configuration_settings(self):
|
||||
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from prowler.providers.aws.services.lightsail.lightsail_service import Instance
|
||||
from tests.providers.aws.utils import (
|
||||
AWS_REGION_US_EAST_1,
|
||||
AWS_REGION_US_EAST_1_AZA,
|
||||
BASE_LIGHTSAIL_ARN,
|
||||
set_mocked_aws_provider,
|
||||
)
|
||||
|
||||
CHECK_PATH = "prowler.providers.aws.services.lightsail.lightsail_instance_php_blueprint_updated.lightsail_instance_php_blueprint_updated"
|
||||
INSTANCE_ARN = f"{BASE_LIGHTSAIL_ARN}:Instance/test-instance"
|
||||
|
||||
|
||||
def _instance(blueprint_id, blueprint_name=""):
|
||||
return Instance(
|
||||
name="test-instance",
|
||||
id="1234/5678",
|
||||
arn=INSTANCE_ARN,
|
||||
tags=[],
|
||||
region=AWS_REGION_US_EAST_1,
|
||||
availability_zone=AWS_REGION_US_EAST_1_AZA,
|
||||
static_ip=False,
|
||||
public_ip="",
|
||||
private_ip="172.16.2.3",
|
||||
ipv6_addresses=[],
|
||||
ip_address_type="ipv4",
|
||||
ports=[],
|
||||
auto_snapshot=False,
|
||||
blueprint_id=blueprint_id,
|
||||
blueprint_name=blueprint_name,
|
||||
)
|
||||
|
||||
|
||||
def _run_check(instances, audit_config=None):
|
||||
lightsail_client = MagicMock
|
||||
lightsail_client.instances = instances
|
||||
lightsail_client.audit_config = audit_config or {}
|
||||
|
||||
with (
|
||||
patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_aws_provider([AWS_REGION_US_EAST_1]),
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.aws.services.lightsail.lightsail_service.Lightsail",
|
||||
new=lightsail_client,
|
||||
),
|
||||
patch(f"{CHECK_PATH}.lightsail_client", new=lightsail_client),
|
||||
):
|
||||
from prowler.providers.aws.services.lightsail.lightsail_instance_php_blueprint_updated.lightsail_instance_php_blueprint_updated import (
|
||||
lightsail_instance_php_blueprint_updated,
|
||||
)
|
||||
|
||||
return lightsail_instance_php_blueprint_updated().execute()
|
||||
|
||||
|
||||
class Test_lightsail_instance_php_blueprint_updated:
|
||||
def test_no_instances(self):
|
||||
assert len(_run_check({})) == 0
|
||||
|
||||
@pytest.mark.parametrize("blueprint_id", ["ubuntu_22_04", "nodejs", "mean", ""])
|
||||
def test_non_php_blueprint_is_skipped(self, blueprint_id):
|
||||
assert len(_run_check({INSTANCE_ARN: _instance(blueprint_id)})) == 0
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"blueprint_id",
|
||||
["wordpress", "wordpress_multisite", "lamp_8_bitnami", "nginx", "drupal"],
|
||||
)
|
||||
def test_php_blueprint_is_manual(self, blueprint_id):
|
||||
result = _run_check({INSTANCE_ARN: _instance(blueprint_id, "WordPress")})
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "MANUAL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Lightsail instance test-instance was created from the PHP blueprint '{blueprint_id}'; the PHP runtime cannot be inspected through the API, verify over SSH that it runs a fixed release (8.2.35, 8.3.36, 8.4.27, 8.5.12 or newer); Bitnami-packaged blueprints bundle a PHP copy that apt never updates, so migrate them to the Lightsail-packaged blueprint or replace the bundled PHP with a package-managed one."
|
||||
)
|
||||
assert result[0].resource_id == "1234/5678"
|
||||
assert result[0].resource_arn == INSTANCE_ARN
|
||||
assert result[0].region == AWS_REGION_US_EAST_1
|
||||
|
||||
def test_custom_blueprint_list(self):
|
||||
instances = {INSTANCE_ARN: _instance("custom_php_app")}
|
||||
assert len(_run_check(instances)) == 0
|
||||
result = _run_check(instances, {"lightsail_php_blueprints": ["custom_php"]})
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "MANUAL"
|
||||
@@ -202,6 +202,14 @@ class TestLightsailService:
|
||||
== f"{BASE_LIGHTSAIL_ARN}:Instance/test-id"
|
||||
)
|
||||
assert lightsail.instances[f"{BASE_LIGHTSAIL_ARN}:Instance/test-id"].tags == []
|
||||
assert (
|
||||
lightsail.instances[f"{BASE_LIGHTSAIL_ARN}:Instance/test-id"].blueprint_id
|
||||
== "wordpress"
|
||||
)
|
||||
assert (
|
||||
lightsail.instances[f"{BASE_LIGHTSAIL_ARN}:Instance/test-id"].blueprint_name
|
||||
== "WordPress"
|
||||
)
|
||||
assert (
|
||||
lightsail.instances[f"{BASE_LIGHTSAIL_ARN}:Instance/test-id"].region
|
||||
== AWS_REGION_US_EAST_1
|
||||
|
||||
@@ -207,7 +207,24 @@ class Test_WAFv2_Service:
|
||||
"CloudWatchMetricsEnabled": True,
|
||||
"MetricName": "web-acl-test-metric",
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "AWS-AWSManagedRulesPHPRuleSet",
|
||||
"Priority": 2,
|
||||
"Statement": {
|
||||
"ManagedRuleGroupStatement": {
|
||||
"VendorName": "AWS",
|
||||
"Name": "AWSManagedRulesPHPRuleSet",
|
||||
"Version": "Version_2.3",
|
||||
}
|
||||
},
|
||||
"OverrideAction": {"None": {}},
|
||||
"VisibilityConfig": {
|
||||
"SampledRequestsEnabled": True,
|
||||
"CloudWatchMetricsEnabled": True,
|
||||
"MetricName": "php-rule-set",
|
||||
},
|
||||
},
|
||||
],
|
||||
VisibilityConfig={
|
||||
"SampledRequestsEnabled": False,
|
||||
@@ -221,6 +238,11 @@ class Test_WAFv2_Service:
|
||||
aws = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
|
||||
wafv2 = WAFv2(aws)
|
||||
assert len(wafv2.web_acls) == 1
|
||||
assert len(wafv2.web_acls[waf_arn].rules) == 1
|
||||
assert len(wafv2.web_acls[waf_arn].rules) == 2
|
||||
assert wafv2.web_acls[waf_arn].rules[0].name == "rule-on"
|
||||
assert wafv2.web_acls[waf_arn].rules[0].cloudwatch_metrics_enabled
|
||||
assert wafv2.web_acls[waf_arn].rules[0].managed_rule_group is None
|
||||
managed = wafv2.web_acls[waf_arn].rules[1].managed_rule_group
|
||||
assert managed.vendor_name == "AWS"
|
||||
assert managed.name == "AWSManagedRulesPHPRuleSet"
|
||||
assert managed.version == "Version_2.3"
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
from unittest import mock
|
||||
|
||||
from prowler.providers.aws.services.wafv2.wafv2_service import (
|
||||
ManagedRuleGroup,
|
||||
Rule,
|
||||
WebAclv2,
|
||||
)
|
||||
from tests.providers.aws.utils import (
|
||||
AWS_ACCOUNT_NUMBER,
|
||||
AWS_REGION_US_EAST_1,
|
||||
set_mocked_aws_provider,
|
||||
)
|
||||
|
||||
CHECK_PATH = "prowler.providers.aws.services.wafv2.wafv2_webacl_managed_rule_groups_enabled.wafv2_webacl_managed_rule_groups_enabled"
|
||||
WEB_ACL_ARN = f"arn:aws:wafv2:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:regional/webacl/test-acl/abc"
|
||||
|
||||
|
||||
def _web_acl(rules):
|
||||
return WebAclv2(
|
||||
arn=WEB_ACL_ARN,
|
||||
name="test-acl",
|
||||
id="abc",
|
||||
albs=[],
|
||||
user_pools=[],
|
||||
region=AWS_REGION_US_EAST_1,
|
||||
tags=[],
|
||||
rules=rules,
|
||||
)
|
||||
|
||||
|
||||
def _run_check(web_acls):
|
||||
wafv2_client = mock.MagicMock()
|
||||
wafv2_client.web_acls = web_acls
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_aws_provider([AWS_REGION_US_EAST_1]),
|
||||
),
|
||||
mock.patch(f"{CHECK_PATH}.wafv2_client", new=wafv2_client),
|
||||
):
|
||||
from prowler.providers.aws.services.wafv2.wafv2_webacl_managed_rule_groups_enabled.wafv2_webacl_managed_rule_groups_enabled import (
|
||||
wafv2_webacl_managed_rule_groups_enabled,
|
||||
)
|
||||
|
||||
return wafv2_webacl_managed_rule_groups_enabled().execute()
|
||||
|
||||
|
||||
class Test_wafv2_webacl_managed_rule_groups_enabled:
|
||||
def test_no_web_acls(self):
|
||||
assert len(_run_check({})) == 0
|
||||
|
||||
def test_no_managed_rule_groups(self):
|
||||
result = _run_check({WEB_ACL_ARN: _web_acl([Rule(name="custom-rule")])})
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "AWS WAFv2 Web ACL test-acl does not use any managed rule group."
|
||||
)
|
||||
assert result[0].resource_id == "abc"
|
||||
assert result[0].resource_arn == WEB_ACL_ARN
|
||||
assert result[0].region == AWS_REGION_US_EAST_1
|
||||
|
||||
def test_managed_rule_groups(self):
|
||||
rules = [
|
||||
Rule(name="custom-rule"),
|
||||
Rule(
|
||||
name="AWS-AWSManagedRulesCommonRuleSet",
|
||||
managed_rule_group=ManagedRuleGroup(
|
||||
vendor_name="AWS", name="AWSManagedRulesCommonRuleSet"
|
||||
),
|
||||
),
|
||||
Rule(
|
||||
name="AWS-AWSManagedRulesPHPRuleSet",
|
||||
managed_rule_group=ManagedRuleGroup(
|
||||
vendor_name="AWS",
|
||||
name="AWSManagedRulesPHPRuleSet",
|
||||
version="Version_2.3",
|
||||
),
|
||||
),
|
||||
]
|
||||
result = _run_check({WEB_ACL_ARN: _web_acl(rules)})
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "AWS WAFv2 Web ACL test-acl uses managed rule groups: AWS/AWSManagedRulesCommonRuleSet, AWS/AWSManagedRulesPHPRuleSet."
|
||||
)
|
||||
@@ -0,0 +1,54 @@
|
||||
import pytest
|
||||
|
||||
from prowler.providers.azure.lib.runtime.runtime_version import (
|
||||
parse_java_version,
|
||||
parse_runtime_version,
|
||||
runtime_version_at_least,
|
||||
)
|
||||
|
||||
|
||||
class Test_runtime_version_helpers:
|
||||
@pytest.mark.parametrize(
|
||||
"value,expected",
|
||||
[
|
||||
("PHP|8.3", (8, 3)),
|
||||
("php|8.0", (8, 0)),
|
||||
("PYTHON|3.12", (3, 12)),
|
||||
("8.2.35", (8, 2, 35)),
|
||||
("", ()),
|
||||
(None, ()),
|
||||
("PHP|latest", ()),
|
||||
],
|
||||
)
|
||||
def test_parse_runtime_version(self, value, expected):
|
||||
assert parse_runtime_version(value) == expected
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"value,expected",
|
||||
[
|
||||
("JAVA|17-java17", (17,)),
|
||||
("TOMCAT|10.0-java11", (11,)),
|
||||
("JBOSSEAP|7-java8", (8,)),
|
||||
("JAVA|8-jre8", (8,)),
|
||||
("java1.8", (8,)),
|
||||
("17", (17,)),
|
||||
("", ()),
|
||||
],
|
||||
)
|
||||
def test_parse_java_version(self, value, expected):
|
||||
assert parse_java_version(value) == expected
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"current,minimum,expected",
|
||||
[
|
||||
((8, 3), (8, 2), True),
|
||||
((8, 2), (8, 2), True),
|
||||
((8, 1), (8, 2), False),
|
||||
((8, 2), (8, 2, 35), True),
|
||||
((9,), (8, 2), True),
|
||||
((), (8, 2), False),
|
||||
((8, 2), (), False),
|
||||
],
|
||||
)
|
||||
def test_runtime_version_at_least(self, current, minimum, expected):
|
||||
assert runtime_version_at_least(current, minimum) is expected
|
||||
+144
-310
@@ -1,6 +1,8 @@
|
||||
from unittest import mock
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
|
||||
from tests.providers.azure.azure_fixtures import (
|
||||
AZURE_SUBSCRIPTION_DISPLAY,
|
||||
AZURE_SUBSCRIPTION_ID,
|
||||
@@ -8,333 +10,165 @@ from tests.providers.azure.azure_fixtures import (
|
||||
set_mocked_azure_provider,
|
||||
)
|
||||
|
||||
CHECK_PATH = "prowler.providers.azure.services.app.app_ensure_java_version_is_latest.app_ensure_java_version_is_latest"
|
||||
|
||||
|
||||
def _run_check(app_client):
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(f"{CHECK_PATH}.app_client", new=app_client),
|
||||
):
|
||||
from prowler.providers.azure.services.app.app_ensure_java_version_is_latest.app_ensure_java_version_is_latest import (
|
||||
app_ensure_java_version_is_latest,
|
||||
)
|
||||
|
||||
return app_ensure_java_version_is_latest().execute()
|
||||
|
||||
|
||||
def _app_client(configurations, audit_config=None):
|
||||
# app_service imports monitor_client, which needs the provider patched at import time
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
):
|
||||
from prowler.providers.azure.services.app.app_service import WebApp
|
||||
|
||||
resource_id = f"/subscriptions/{uuid4()}"
|
||||
app_client = mock.MagicMock()
|
||||
app_client.subscriptions = {AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_NAME}
|
||||
app_client.audit_config = audit_config or {}
|
||||
app_client.apps = {
|
||||
AZURE_SUBSCRIPTION_ID: {
|
||||
resource_id: WebApp(
|
||||
resource_id=resource_id,
|
||||
name="app_id-1",
|
||||
auth_enabled=True,
|
||||
configurations=configurations,
|
||||
client_cert_mode="Ignore",
|
||||
https_only=False,
|
||||
identity=None,
|
||||
location="West Europe",
|
||||
)
|
||||
}
|
||||
}
|
||||
return app_client, resource_id
|
||||
|
||||
|
||||
class Test_app_ensure_java_version_is_latest:
|
||||
def test_app_no_subscriptions(self):
|
||||
app_client = mock.MagicMock
|
||||
app_client = mock.MagicMock()
|
||||
app_client.subscriptions = {AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_NAME}
|
||||
app_client.apps = {}
|
||||
assert len(_run_check(app_client)) == 0
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.app.app_ensure_java_version_is_latest.app_ensure_java_version_is_latest.app_client",
|
||||
new=app_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.app.app_ensure_java_version_is_latest.app_ensure_java_version_is_latest import (
|
||||
app_ensure_java_version_is_latest,
|
||||
)
|
||||
|
||||
check = app_ensure_java_version_is_latest()
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
|
||||
def test_app_subscriptions_empty(self):
|
||||
app_client = mock.MagicMock
|
||||
def test_app_subscription_empty(self):
|
||||
app_client = mock.MagicMock()
|
||||
app_client.subscriptions = {AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_NAME}
|
||||
app_client.apps = {AZURE_SUBSCRIPTION_ID: {}}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.app.app_ensure_java_version_is_latest.app_ensure_java_version_is_latest.app_client",
|
||||
new=app_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.app.app_ensure_java_version_is_latest.app_ensure_java_version_is_latest import (
|
||||
app_ensure_java_version_is_latest,
|
||||
)
|
||||
|
||||
check = app_ensure_java_version_is_latest()
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
assert len(_run_check(app_client)) == 0
|
||||
|
||||
def test_app_configurations_none(self):
|
||||
resource_id = f"/subscriptions/{uuid4()}"
|
||||
app_client = mock.MagicMock
|
||||
app_client.subscriptions = {AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_NAME}
|
||||
app_client, _ = _app_client(None)
|
||||
assert len(_run_check(app_client)) == 0
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
def test_app_linux_fx_version_none_is_skipped(self):
|
||||
app_client, _ = _app_client(
|
||||
mock.MagicMock(linux_fx_version=None, java_version=None)
|
||||
)
|
||||
assert len(_run_check(app_client)) == 0
|
||||
|
||||
def test_app_linux_fx_version_none_uses_windows_version(self):
|
||||
app_client, _ = _app_client(
|
||||
mock.MagicMock(linux_fx_version=None, java_version="17")
|
||||
)
|
||||
result = _run_check(app_client)
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
|
||||
def test_app_other_runtime_is_skipped(self):
|
||||
app_client, _ = _app_client(
|
||||
mock.MagicMock(linux_fx_version="php|8.0", java_version=None)
|
||||
)
|
||||
assert len(_run_check(app_client)) == 0
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"configurations,minimum,expected_status,expected_message",
|
||||
[
|
||||
(
|
||||
{"linux_fx_version": "Tomcat|9.0-java11", "java_version": None},
|
||||
"17",
|
||||
"FAIL",
|
||||
"Java version is set to 'Tomcat|9.0-java11', which is below the minimum supported version 'java 17',",
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.app.app_ensure_java_version_is_latest.app_ensure_java_version_is_latest.app_client",
|
||||
new=app_client,
|
||||
(
|
||||
{"linux_fx_version": "Tomcat|9.0-java17", "java_version": None},
|
||||
"17",
|
||||
"PASS",
|
||||
"Java version is set to 'Tomcat|9.0-java17', which meets the minimum supported version 'java 17',",
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.app.app_ensure_java_version_is_latest.app_ensure_java_version_is_latest import (
|
||||
app_ensure_java_version_is_latest,
|
||||
)
|
||||
from prowler.providers.azure.services.app.app_service import WebApp
|
||||
|
||||
app_client.apps = {
|
||||
AZURE_SUBSCRIPTION_ID: {
|
||||
resource_id: WebApp(
|
||||
resource_id=resource_id,
|
||||
name="app_id-1",
|
||||
auth_enabled=True,
|
||||
configurations=None,
|
||||
client_cert_mode="Ignore",
|
||||
https_only=False,
|
||||
identity=None,
|
||||
location="West Europe",
|
||||
)
|
||||
}
|
||||
}
|
||||
check = app_ensure_java_version_is_latest()
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
|
||||
def test_app_linux_java_version_latest(self):
|
||||
resource_id = f"/subscriptions/{uuid4()}"
|
||||
app_client = mock.MagicMock
|
||||
app_client.subscriptions = {AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_NAME}
|
||||
|
||||
app_client.audit_config = {"java_latest_version": "17"}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
(
|
||||
{"linux_fx_version": "JAVA|21-java21", "java_version": None},
|
||||
"17",
|
||||
"PASS",
|
||||
"Java version is set to 'JAVA|21-java21', which meets the minimum supported version 'java 17',",
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.app.app_ensure_java_version_is_latest.app_ensure_java_version_is_latest.app_client",
|
||||
new=app_client,
|
||||
(
|
||||
{"linux_fx_version": "JAVA|8-jre8", "java_version": None},
|
||||
"17",
|
||||
"FAIL",
|
||||
"Java version is set to 'JAVA|8-jre8', which is below the minimum supported version 'java 17',",
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.app.app_ensure_java_version_is_latest.app_ensure_java_version_is_latest import (
|
||||
app_ensure_java_version_is_latest,
|
||||
)
|
||||
from prowler.providers.azure.services.app.app_service import WebApp
|
||||
|
||||
app_client.apps = {
|
||||
AZURE_SUBSCRIPTION_ID: {
|
||||
resource_id: WebApp(
|
||||
resource_id=resource_id,
|
||||
name="app_id-1",
|
||||
auth_enabled=True,
|
||||
configurations=mock.MagicMock(
|
||||
linux_fx_version="Tomcat|9.0-java17", java_version=None
|
||||
),
|
||||
client_cert_mode="Ignore",
|
||||
https_only=False,
|
||||
identity=None,
|
||||
location="West Europe",
|
||||
)
|
||||
}
|
||||
}
|
||||
check = app_ensure_java_version_is_latest()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Java version is set to 'java 17' for app 'app_id-1' in subscription '{AZURE_SUBSCRIPTION_DISPLAY}'."
|
||||
)
|
||||
assert result[0].resource_id == resource_id
|
||||
assert result[0].resource_name == "app_id-1"
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].location == "West Europe"
|
||||
|
||||
def test_app_linux_java_version_not_latest(self):
|
||||
resource_id = f"/subscriptions/{uuid4()}"
|
||||
app_client = mock.MagicMock
|
||||
app_client.subscriptions = {AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_NAME}
|
||||
|
||||
app_client.audit_config = {"java_latest_version": "17"}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
(
|
||||
{"linux_fx_version": "JBOSSEAP|7-java8", "java_version": None},
|
||||
"11",
|
||||
"FAIL",
|
||||
"Java version is set to 'JBOSSEAP|7-java8', which is below the minimum supported version 'java 11',",
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.app.app_ensure_java_version_is_latest.app_ensure_java_version_is_latest.app_client",
|
||||
new=app_client,
|
||||
(
|
||||
{"linux_fx_version": "", "java_version": "11"},
|
||||
"17",
|
||||
"FAIL",
|
||||
"Java version is set to 'java11', which is below the minimum supported version 'java 17',",
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.app.app_ensure_java_version_is_latest.app_ensure_java_version_is_latest import (
|
||||
app_ensure_java_version_is_latest,
|
||||
)
|
||||
from prowler.providers.azure.services.app.app_service import WebApp
|
||||
|
||||
app_client.apps = {
|
||||
AZURE_SUBSCRIPTION_ID: {
|
||||
resource_id: WebApp(
|
||||
resource_id=resource_id,
|
||||
name="app_id-1",
|
||||
auth_enabled=True,
|
||||
configurations=mock.MagicMock(
|
||||
linux_fx_version="Tomcat|9.0-java11", java_version=None
|
||||
),
|
||||
client_cert_mode="Ignore",
|
||||
https_only=False,
|
||||
identity=None,
|
||||
location="West Europe",
|
||||
)
|
||||
}
|
||||
}
|
||||
check = app_ensure_java_version_is_latest()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Java version is set to 'Tomcat|9.0-java11', but should be set to 'java 17' for app 'app_id-1' in subscription '{AZURE_SUBSCRIPTION_DISPLAY}'."
|
||||
)
|
||||
assert result[0].resource_id == resource_id
|
||||
assert result[0].resource_name == "app_id-1"
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].location == "West Europe"
|
||||
|
||||
def test_app_windows_java_version_latest(self):
|
||||
resource_id = f"/subscriptions/{uuid4()}"
|
||||
app_client = mock.MagicMock
|
||||
app_client.subscriptions = {AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_NAME}
|
||||
|
||||
app_client.audit_config = {"java_latest_version": "17"}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
(
|
||||
{"linux_fx_version": "", "java_version": "17"},
|
||||
"17",
|
||||
"PASS",
|
||||
"Java version is set to 'java17', which meets the minimum supported version 'java 17',",
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.app.app_ensure_java_version_is_latest.app_ensure_java_version_is_latest.app_client",
|
||||
new=app_client,
|
||||
(
|
||||
{"linux_fx_version": "", "java_version": "1.8"},
|
||||
"17",
|
||||
"FAIL",
|
||||
"Java version is set to 'java1.8', which is below the minimum supported version 'java 17',",
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.app.app_ensure_java_version_is_latest.app_ensure_java_version_is_latest import (
|
||||
app_ensure_java_version_is_latest,
|
||||
)
|
||||
from prowler.providers.azure.services.app.app_service import WebApp
|
||||
],
|
||||
)
|
||||
def test_app_java_version(
|
||||
self, configurations, minimum, expected_status, expected_message
|
||||
):
|
||||
app_client, resource_id = _app_client(
|
||||
mock.MagicMock(**configurations), {"java_latest_version": minimum}
|
||||
)
|
||||
result = _run_check(app_client)
|
||||
assert len(result) == 1
|
||||
assert result[0].status == expected_status
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"{expected_message} for app 'app_id-1' in subscription '{AZURE_SUBSCRIPTION_DISPLAY}'."
|
||||
)
|
||||
assert result[0].resource_id == resource_id
|
||||
assert result[0].resource_name == "app_id-1"
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].location == "West Europe"
|
||||
|
||||
app_client.apps = {
|
||||
AZURE_SUBSCRIPTION_ID: {
|
||||
resource_id: WebApp(
|
||||
resource_id=resource_id,
|
||||
name="app_id-1",
|
||||
auth_enabled=True,
|
||||
configurations=mock.MagicMock(
|
||||
linux_fx_version="", java_version="17"
|
||||
),
|
||||
client_cert_mode="Ignore",
|
||||
https_only=False,
|
||||
identity=None,
|
||||
location="West Europe",
|
||||
)
|
||||
}
|
||||
}
|
||||
check = app_ensure_java_version_is_latest()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Java version is set to 'java 17' for app 'app_id-1' in subscription '{AZURE_SUBSCRIPTION_DISPLAY}'."
|
||||
)
|
||||
assert result[0].resource_id == resource_id
|
||||
assert result[0].resource_name == "app_id-1"
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].location == "West Europe"
|
||||
|
||||
def test_app_windows_java_version_not_latest(self):
|
||||
resource_id = f"/subscriptions/{uuid4()}"
|
||||
app_client = mock.MagicMock
|
||||
app_client.subscriptions = {AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_NAME}
|
||||
app_client.audit_config = {"java_latest_version": "17"}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.app.app_ensure_java_version_is_latest.app_ensure_java_version_is_latest.app_client",
|
||||
new=app_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.app.app_ensure_java_version_is_latest.app_ensure_java_version_is_latest import (
|
||||
app_ensure_java_version_is_latest,
|
||||
)
|
||||
from prowler.providers.azure.services.app.app_service import WebApp
|
||||
|
||||
app_client.apps = {
|
||||
AZURE_SUBSCRIPTION_ID: {
|
||||
resource_id: WebApp(
|
||||
resource_id=resource_id,
|
||||
name="app_id-1",
|
||||
auth_enabled=True,
|
||||
configurations=mock.MagicMock(
|
||||
linux_fx_version="", java_version="11"
|
||||
),
|
||||
client_cert_mode="Ignore",
|
||||
https_only=False,
|
||||
identity=None,
|
||||
location="West Europe",
|
||||
)
|
||||
}
|
||||
}
|
||||
check = app_ensure_java_version_is_latest()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Java version is set to 'java11', but should be set to 'java 17' for app 'app_id-1' in subscription '{AZURE_SUBSCRIPTION_DISPLAY}'."
|
||||
)
|
||||
assert result[0].resource_id == resource_id
|
||||
assert result[0].resource_name == "app_id-1"
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].location == "West Europe"
|
||||
|
||||
def test_app_linux_php_version_latest(self):
|
||||
resource_id = f"/subscriptions/{uuid4()}"
|
||||
app_client = mock.MagicMock
|
||||
app_client.subscriptions = {AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_NAME}
|
||||
|
||||
app_client.audit_config = {"java_latest_version": "17"}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.app.app_ensure_java_version_is_latest.app_ensure_java_version_is_latest.app_client",
|
||||
new=app_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.app.app_ensure_java_version_is_latest.app_ensure_java_version_is_latest import (
|
||||
app_ensure_java_version_is_latest,
|
||||
)
|
||||
from prowler.providers.azure.services.app.app_service import WebApp
|
||||
|
||||
app_client.apps = {
|
||||
AZURE_SUBSCRIPTION_ID: {
|
||||
resource_id: WebApp(
|
||||
resource_id=resource_id,
|
||||
name="app_id-1",
|
||||
auth_enabled=True,
|
||||
configurations=mock.MagicMock(
|
||||
linux_fx_version="php|8.0", java_version=None
|
||||
),
|
||||
client_cert_mode="Ignore",
|
||||
https_only=False,
|
||||
identity=None,
|
||||
location="West Europe",
|
||||
)
|
||||
}
|
||||
}
|
||||
check = app_ensure_java_version_is_latest()
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
def test_app_default_minimum(self):
|
||||
app_client, _ = _app_client(
|
||||
mock.MagicMock(linux_fx_version="JAVA|21-java21", java_version=None)
|
||||
)
|
||||
result = _run_check(app_client)
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
+157
-164
@@ -1,6 +1,8 @@
|
||||
from unittest import mock
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
|
||||
from tests.providers.azure.azure_fixtures import (
|
||||
AZURE_SUBSCRIPTION_DISPLAY,
|
||||
AZURE_SUBSCRIPTION_ID,
|
||||
@@ -8,186 +10,177 @@ from tests.providers.azure.azure_fixtures import (
|
||||
set_mocked_azure_provider,
|
||||
)
|
||||
|
||||
CHECK_PATH = "prowler.providers.azure.services.app.app_ensure_php_version_is_latest.app_ensure_php_version_is_latest"
|
||||
|
||||
|
||||
def _run_check(app_client):
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(f"{CHECK_PATH}.app_client", new=app_client),
|
||||
):
|
||||
from prowler.providers.azure.services.app.app_ensure_php_version_is_latest.app_ensure_php_version_is_latest import (
|
||||
app_ensure_php_version_is_latest,
|
||||
)
|
||||
|
||||
return app_ensure_php_version_is_latest().execute()
|
||||
|
||||
|
||||
def _app_client(configurations, audit_config=None):
|
||||
# app_service imports monitor_client, which needs the provider patched at import time
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
):
|
||||
from prowler.providers.azure.services.app.app_service import WebApp
|
||||
|
||||
resource_id = f"/subscriptions/{uuid4()}"
|
||||
app_client = mock.MagicMock()
|
||||
app_client.subscriptions = {AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_NAME}
|
||||
app_client.audit_config = audit_config or {}
|
||||
app_client.apps = {
|
||||
AZURE_SUBSCRIPTION_ID: {
|
||||
resource_id: WebApp(
|
||||
resource_id=resource_id,
|
||||
name="app_id-1",
|
||||
auth_enabled=True,
|
||||
configurations=configurations,
|
||||
client_cert_mode="Ignore",
|
||||
https_only=False,
|
||||
identity=None,
|
||||
location="West Europe",
|
||||
)
|
||||
}
|
||||
}
|
||||
return app_client, resource_id
|
||||
|
||||
|
||||
class Test_app_ensure_php_version_is_latest:
|
||||
def test_app_no_subscriptions(self):
|
||||
app_client = mock.MagicMock
|
||||
app_client = mock.MagicMock()
|
||||
app_client.subscriptions = {AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_NAME}
|
||||
app_client.apps = {}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.app.app_ensure_php_version_is_latest.app_ensure_php_version_is_latest.app_client",
|
||||
new=app_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.app.app_ensure_php_version_is_latest.app_ensure_php_version_is_latest import (
|
||||
app_ensure_php_version_is_latest,
|
||||
)
|
||||
|
||||
check = app_ensure_php_version_is_latest()
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
assert len(_run_check(app_client)) == 0
|
||||
|
||||
def test_app_subscription_empty(self):
|
||||
app_client = mock.MagicMock
|
||||
app_client = mock.MagicMock()
|
||||
app_client.subscriptions = {AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_NAME}
|
||||
app_client.apps = {AZURE_SUBSCRIPTION_ID: {}}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.app.app_ensure_php_version_is_latest.app_ensure_php_version_is_latest.app_client",
|
||||
new=app_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.app.app_ensure_php_version_is_latest.app_ensure_php_version_is_latest import (
|
||||
app_ensure_php_version_is_latest,
|
||||
)
|
||||
|
||||
check = app_ensure_php_version_is_latest()
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
assert len(_run_check(app_client)) == 0
|
||||
|
||||
def test_app_configurations_none(self):
|
||||
resource_id = f"/subscriptions/{uuid4()}"
|
||||
app_client = mock.MagicMock
|
||||
app_client.subscriptions = {AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_NAME}
|
||||
app_client, _ = _app_client(None)
|
||||
assert len(_run_check(app_client)) == 0
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
def test_app_linux_fx_version_none_is_skipped(self):
|
||||
app_client, _ = _app_client(
|
||||
mock.MagicMock(linux_fx_version=None, php_version="")
|
||||
)
|
||||
assert len(_run_check(app_client)) == 0
|
||||
|
||||
def test_app_linux_fx_version_none_uses_windows_version(self):
|
||||
app_client, _ = _app_client(
|
||||
mock.MagicMock(linux_fx_version=None, php_version="8.5")
|
||||
)
|
||||
result = _run_check(app_client)
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
|
||||
def test_app_other_runtime_is_skipped(self):
|
||||
app_client, _ = _app_client(
|
||||
mock.MagicMock(linux_fx_version="PYTHON|3.12", php_version="")
|
||||
)
|
||||
assert len(_run_check(app_client)) == 0
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"configurations,minimum,expected_status,expected_message",
|
||||
[
|
||||
(
|
||||
{"linux_fx_version": "php|8.0", "php_version": ""},
|
||||
"8.2",
|
||||
"FAIL",
|
||||
"PHP version is set to 'php|8.0', which is below the minimum supported version '8.2',",
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.app.app_ensure_php_version_is_latest.app_ensure_php_version_is_latest.app_client",
|
||||
new=app_client,
|
||||
(
|
||||
{"linux_fx_version": "PHP|8.1", "php_version": ""},
|
||||
"8.2",
|
||||
"FAIL",
|
||||
"PHP version is set to 'PHP|8.1', which is below the minimum supported version '8.2',",
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.app.app_ensure_php_version_is_latest.app_ensure_php_version_is_latest import (
|
||||
app_ensure_php_version_is_latest,
|
||||
)
|
||||
from prowler.providers.azure.services.app.app_service import WebApp
|
||||
|
||||
app_client.apps = {
|
||||
AZURE_SUBSCRIPTION_ID: {
|
||||
resource_id: WebApp(
|
||||
resource_id=resource_id,
|
||||
name="app_id-1",
|
||||
auth_enabled=True,
|
||||
configurations=None,
|
||||
client_cert_mode="Ignore",
|
||||
https_only=False,
|
||||
identity=None,
|
||||
location="West Europe",
|
||||
)
|
||||
}
|
||||
}
|
||||
check = app_ensure_php_version_is_latest()
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
|
||||
def test_app_php_version_not_latest(self):
|
||||
resource_id = f"/subscriptions/{uuid4()}"
|
||||
app_client = mock.MagicMock
|
||||
app_client.subscriptions = {AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_NAME}
|
||||
|
||||
app_client.audit_config = {"php_latest_version": "8.2"}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
(
|
||||
{"linux_fx_version": "PHP|8.2", "php_version": ""},
|
||||
"8.2",
|
||||
"PASS",
|
||||
"PHP version is set to 'PHP|8.2', which meets the minimum supported version '8.2',",
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.app.app_ensure_php_version_is_latest.app_ensure_php_version_is_latest.app_client",
|
||||
new=app_client,
|
||||
(
|
||||
{"linux_fx_version": "PHP|8.3", "php_version": ""},
|
||||
"8.2",
|
||||
"PASS",
|
||||
"PHP version is set to 'PHP|8.3', which meets the minimum supported version '8.2',",
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.app.app_ensure_php_version_is_latest.app_ensure_php_version_is_latest import (
|
||||
app_ensure_php_version_is_latest,
|
||||
)
|
||||
from prowler.providers.azure.services.app.app_service import WebApp
|
||||
|
||||
app_client.apps = {
|
||||
AZURE_SUBSCRIPTION_ID: {
|
||||
resource_id: WebApp(
|
||||
resource_id=resource_id,
|
||||
name="app_id-1",
|
||||
auth_enabled=True,
|
||||
configurations=mock.MagicMock(linux_fx_version="php|8.0"),
|
||||
client_cert_mode="Ignore",
|
||||
https_only=False,
|
||||
identity=None,
|
||||
location="West Europe",
|
||||
)
|
||||
}
|
||||
}
|
||||
check = app_ensure_php_version_is_latest()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"PHP version is set to 'php|8.0', the latest version that you could use is the '8.2' version, for app 'app_id-1' in subscription '{AZURE_SUBSCRIPTION_DISPLAY}'."
|
||||
)
|
||||
assert result[0].resource_id == resource_id
|
||||
assert result[0].resource_name == "app_id-1"
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].location == "West Europe"
|
||||
|
||||
def test_app_php_version_latest(self):
|
||||
resource_id = f"/subscriptions/{uuid4()}"
|
||||
app_client = mock.MagicMock
|
||||
app_client.subscriptions = {AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_NAME}
|
||||
|
||||
app_client.audit_config = {"php_latest_version": "8.2"}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
(
|
||||
{"linux_fx_version": "PHP|8.5", "php_version": ""},
|
||||
"8.2",
|
||||
"PASS",
|
||||
"PHP version is set to 'PHP|8.5', which meets the minimum supported version '8.2',",
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.app.app_ensure_php_version_is_latest.app_ensure_php_version_is_latest.app_client",
|
||||
new=app_client,
|
||||
(
|
||||
{"linux_fx_version": "PHP|8.3", "php_version": ""},
|
||||
"8.4",
|
||||
"FAIL",
|
||||
"PHP version is set to 'PHP|8.3', which is below the minimum supported version '8.4',",
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.app.app_ensure_php_version_is_latest.app_ensure_php_version_is_latest import (
|
||||
app_ensure_php_version_is_latest,
|
||||
)
|
||||
from prowler.providers.azure.services.app.app_service import WebApp
|
||||
(
|
||||
{"linux_fx_version": "PHP|8.2", "php_version": ""},
|
||||
"8.2.35",
|
||||
"PASS",
|
||||
"PHP version is set to 'PHP|8.2', which meets the minimum supported version '8.2.35',",
|
||||
),
|
||||
(
|
||||
{"linux_fx_version": "", "php_version": "7.4"},
|
||||
"8.2",
|
||||
"FAIL",
|
||||
"PHP version is set to '7.4', which is below the minimum supported version '8.2',",
|
||||
),
|
||||
(
|
||||
{"linux_fx_version": "", "php_version": "8.2"},
|
||||
"8.2",
|
||||
"PASS",
|
||||
"PHP version is set to '8.2', which meets the minimum supported version '8.2',",
|
||||
),
|
||||
(
|
||||
{"linux_fx_version": "PHP|latest", "php_version": ""},
|
||||
"8.2",
|
||||
"FAIL",
|
||||
"PHP version is set to 'PHP|latest', which is below the minimum supported version '8.2',",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_app_php_version(
|
||||
self, configurations, minimum, expected_status, expected_message
|
||||
):
|
||||
app_client, resource_id = _app_client(
|
||||
mock.MagicMock(**configurations), {"php_latest_version": minimum}
|
||||
)
|
||||
result = _run_check(app_client)
|
||||
assert len(result) == 1
|
||||
assert result[0].status == expected_status
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"{expected_message} for app 'app_id-1' in subscription '{AZURE_SUBSCRIPTION_DISPLAY}'."
|
||||
)
|
||||
assert result[0].resource_id == resource_id
|
||||
assert result[0].resource_name == "app_id-1"
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].location == "West Europe"
|
||||
|
||||
app_client.apps = {
|
||||
AZURE_SUBSCRIPTION_ID: {
|
||||
resource_id: WebApp(
|
||||
resource_id=resource_id,
|
||||
name="app_id-1",
|
||||
auth_enabled=True,
|
||||
configurations=mock.MagicMock(linux_fx_version="php|8.2"),
|
||||
client_cert_mode="Ignore",
|
||||
https_only=False,
|
||||
identity=None,
|
||||
location="West Europe",
|
||||
)
|
||||
}
|
||||
}
|
||||
check = app_ensure_php_version_is_latest()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"PHP version is set to '8.2' for app 'app_id-1' in subscription '{AZURE_SUBSCRIPTION_DISPLAY}'."
|
||||
)
|
||||
assert result[0].resource_id == resource_id
|
||||
assert result[0].resource_name == "app_id-1"
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].location == "West Europe"
|
||||
def test_app_default_minimum(self):
|
||||
app_client, _ = _app_client(
|
||||
mock.MagicMock(linux_fx_version="PHP|8.5", php_version="")
|
||||
)
|
||||
result = _run_check(app_client)
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
+135
-165
@@ -1,6 +1,8 @@
|
||||
from unittest import mock
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
|
||||
from tests.providers.azure.azure_fixtures import (
|
||||
AZURE_SUBSCRIPTION_DISPLAY,
|
||||
AZURE_SUBSCRIPTION_ID,
|
||||
@@ -8,185 +10,153 @@ from tests.providers.azure.azure_fixtures import (
|
||||
set_mocked_azure_provider,
|
||||
)
|
||||
|
||||
CHECK_PATH = "prowler.providers.azure.services.app.app_ensure_python_version_is_latest.app_ensure_python_version_is_latest"
|
||||
|
||||
|
||||
def _run_check(app_client):
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(f"{CHECK_PATH}.app_client", new=app_client),
|
||||
):
|
||||
from prowler.providers.azure.services.app.app_ensure_python_version_is_latest.app_ensure_python_version_is_latest import (
|
||||
app_ensure_python_version_is_latest,
|
||||
)
|
||||
|
||||
return app_ensure_python_version_is_latest().execute()
|
||||
|
||||
|
||||
def _app_client(configurations, audit_config=None):
|
||||
# app_service imports monitor_client, which needs the provider patched at import time
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
):
|
||||
from prowler.providers.azure.services.app.app_service import WebApp
|
||||
|
||||
resource_id = f"/subscriptions/{uuid4()}"
|
||||
app_client = mock.MagicMock()
|
||||
app_client.subscriptions = {AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_NAME}
|
||||
app_client.audit_config = audit_config or {}
|
||||
app_client.apps = {
|
||||
AZURE_SUBSCRIPTION_ID: {
|
||||
resource_id: WebApp(
|
||||
resource_id=resource_id,
|
||||
name="app_id-1",
|
||||
auth_enabled=True,
|
||||
configurations=configurations,
|
||||
client_cert_mode="Ignore",
|
||||
https_only=False,
|
||||
identity=None,
|
||||
location="West Europe",
|
||||
)
|
||||
}
|
||||
}
|
||||
return app_client, resource_id
|
||||
|
||||
|
||||
class Test_app_ensure_python_version_is_latest:
|
||||
def test_app_no_subscriptions(self):
|
||||
app_client = mock.MagicMock
|
||||
app_client = mock.MagicMock()
|
||||
app_client.subscriptions = {AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_NAME}
|
||||
app_client.apps = {}
|
||||
assert len(_run_check(app_client)) == 0
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.app.app_ensure_python_version_is_latest.app_ensure_python_version_is_latest.app_client",
|
||||
new=app_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.app.app_ensure_python_version_is_latest.app_ensure_python_version_is_latest import (
|
||||
app_ensure_python_version_is_latest,
|
||||
)
|
||||
|
||||
check = app_ensure_python_version_is_latest()
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
|
||||
def test_app_subscriptions_empty(self):
|
||||
app_client = mock.MagicMock
|
||||
def test_app_subscription_empty(self):
|
||||
app_client = mock.MagicMock()
|
||||
app_client.subscriptions = {AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_NAME}
|
||||
app_client.apps = {AZURE_SUBSCRIPTION_ID: {}}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.app.app_ensure_python_version_is_latest.app_ensure_python_version_is_latest.app_client",
|
||||
new=app_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.app.app_ensure_python_version_is_latest.app_ensure_python_version_is_latest import (
|
||||
app_ensure_python_version_is_latest,
|
||||
)
|
||||
|
||||
check = app_ensure_python_version_is_latest()
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
assert len(_run_check(app_client)) == 0
|
||||
|
||||
def test_app_configurations_none(self):
|
||||
resource_id = f"/subscriptions/{uuid4()}"
|
||||
app_client = mock.MagicMock
|
||||
app_client.subscriptions = {AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_NAME}
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
app_client, _ = _app_client(None)
|
||||
assert len(_run_check(app_client)) == 0
|
||||
|
||||
def test_app_linux_fx_version_none_is_skipped(self):
|
||||
app_client, _ = _app_client(
|
||||
mock.MagicMock(linux_fx_version=None, python_version="")
|
||||
)
|
||||
assert len(_run_check(app_client)) == 0
|
||||
|
||||
def test_app_linux_fx_version_none_uses_windows_version(self):
|
||||
app_client, _ = _app_client(
|
||||
mock.MagicMock(linux_fx_version=None, python_version="3.12")
|
||||
)
|
||||
result = _run_check(app_client)
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
|
||||
def test_app_other_runtime_is_skipped(self):
|
||||
app_client, _ = _app_client(
|
||||
mock.MagicMock(linux_fx_version="PHP|8.2", python_version="")
|
||||
)
|
||||
assert len(_run_check(app_client)) == 0
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"configurations,minimum,expected_status,expected_message",
|
||||
[
|
||||
(
|
||||
{"linux_fx_version": "python|3.10", "python_version": ""},
|
||||
"3.12",
|
||||
"FAIL",
|
||||
"Python version is set to 'python|3.10', which is below the minimum supported version '3.12',",
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.app.app_ensure_python_version_is_latest.app_ensure_python_version_is_latest.app_client",
|
||||
new=app_client,
|
||||
(
|
||||
{"linux_fx_version": "PYTHON|3.12", "python_version": ""},
|
||||
"3.12",
|
||||
"PASS",
|
||||
"Python version is set to 'PYTHON|3.12', which meets the minimum supported version '3.12',",
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.app.app_ensure_python_version_is_latest.app_ensure_python_version_is_latest import (
|
||||
app_ensure_python_version_is_latest,
|
||||
)
|
||||
from prowler.providers.azure.services.app.app_service import WebApp
|
||||
|
||||
app_client.apps = {
|
||||
AZURE_SUBSCRIPTION_ID: {
|
||||
resource_id: WebApp(
|
||||
resource_id=resource_id,
|
||||
name="app_id-1",
|
||||
auth_enabled=True,
|
||||
configurations=None,
|
||||
client_cert_mode="Ignore",
|
||||
https_only=False,
|
||||
identity=None,
|
||||
location="West Europe",
|
||||
)
|
||||
}
|
||||
}
|
||||
check = app_ensure_python_version_is_latest()
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
|
||||
def test_app_python_version_latest(self):
|
||||
resource_id = f"/subscriptions/{uuid4()}"
|
||||
app_client = mock.MagicMock
|
||||
app_client.subscriptions = {AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_NAME}
|
||||
|
||||
app_client.audit_config = {"python_latest_version": "3.12"}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
(
|
||||
{"linux_fx_version": "PYTHON|3.13", "python_version": ""},
|
||||
"3.12",
|
||||
"PASS",
|
||||
"Python version is set to 'PYTHON|3.13', which meets the minimum supported version '3.12',",
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.app.app_ensure_python_version_is_latest.app_ensure_python_version_is_latest.app_client",
|
||||
new=app_client,
|
||||
(
|
||||
{"linux_fx_version": "PYTHON|3.9", "python_version": ""},
|
||||
"3.10",
|
||||
"FAIL",
|
||||
"Python version is set to 'PYTHON|3.9', which is below the minimum supported version '3.10',",
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.app.app_ensure_python_version_is_latest.app_ensure_python_version_is_latest import (
|
||||
app_ensure_python_version_is_latest,
|
||||
)
|
||||
from prowler.providers.azure.services.app.app_service import WebApp
|
||||
|
||||
app_client.apps = {
|
||||
AZURE_SUBSCRIPTION_ID: {
|
||||
resource_id: WebApp(
|
||||
resource_id=resource_id,
|
||||
name="app_id-1",
|
||||
auth_enabled=True,
|
||||
configurations=mock.MagicMock(linux_fx_version="python|3.12"),
|
||||
client_cert_mode="Ignore",
|
||||
https_only=False,
|
||||
identity=None,
|
||||
location="West Europe",
|
||||
)
|
||||
}
|
||||
}
|
||||
check = app_ensure_python_version_is_latest()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Python version is set to '3.12' for app 'app_id-1' in subscription '{AZURE_SUBSCRIPTION_DISPLAY}'."
|
||||
)
|
||||
assert result[0].resource_id == resource_id
|
||||
assert result[0].resource_name == "app_id-1"
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].location == "West Europe"
|
||||
|
||||
def test_app_python_version_not_latest(self):
|
||||
resource_id = f"/subscriptions/{uuid4()}"
|
||||
app_client = mock.MagicMock
|
||||
app_client.subscriptions = {AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_NAME}
|
||||
|
||||
app_client.audit_config = {"python_latest_version": "3.12"}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
(
|
||||
{"linux_fx_version": "", "python_version": "3.4"},
|
||||
"3.12",
|
||||
"FAIL",
|
||||
"Python version is set to '3.4', which is below the minimum supported version '3.12',",
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.app.app_ensure_python_version_is_latest.app_ensure_python_version_is_latest.app_client",
|
||||
new=app_client,
|
||||
(
|
||||
{"linux_fx_version": "", "python_version": "3.12"},
|
||||
"3.12",
|
||||
"PASS",
|
||||
"Python version is set to '3.12', which meets the minimum supported version '3.12',",
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.app.app_ensure_python_version_is_latest.app_ensure_python_version_is_latest import (
|
||||
app_ensure_python_version_is_latest,
|
||||
)
|
||||
from prowler.providers.azure.services.app.app_service import WebApp
|
||||
],
|
||||
)
|
||||
def test_app_python_version(
|
||||
self, configurations, minimum, expected_status, expected_message
|
||||
):
|
||||
app_client, resource_id = _app_client(
|
||||
mock.MagicMock(**configurations), {"python_latest_version": minimum}
|
||||
)
|
||||
result = _run_check(app_client)
|
||||
assert len(result) == 1
|
||||
assert result[0].status == expected_status
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"{expected_message} for app 'app_id-1' in subscription '{AZURE_SUBSCRIPTION_DISPLAY}'."
|
||||
)
|
||||
assert result[0].resource_id == resource_id
|
||||
assert result[0].resource_name == "app_id-1"
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].location == "West Europe"
|
||||
|
||||
app_client.apps = {
|
||||
AZURE_SUBSCRIPTION_ID: {
|
||||
resource_id: WebApp(
|
||||
resource_id=resource_id,
|
||||
name="app_id-1",
|
||||
auth_enabled=True,
|
||||
configurations=mock.MagicMock(linux_fx_version="python|3.10"),
|
||||
client_cert_mode="Ignore",
|
||||
https_only=False,
|
||||
identity=None,
|
||||
location="West Europe",
|
||||
)
|
||||
}
|
||||
}
|
||||
check = app_ensure_python_version_is_latest()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Python version is 'python|3.10', the latest version that you could use is the '3.12' version, for app 'app_id-1' in subscription '{AZURE_SUBSCRIPTION_DISPLAY}'."
|
||||
)
|
||||
assert result[0].resource_id == resource_id
|
||||
assert result[0].resource_name == "app_id-1"
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].location == "West Europe"
|
||||
def test_app_default_minimum(self):
|
||||
app_client, _ = _app_client(
|
||||
mock.MagicMock(linux_fx_version="PYTHON|3.13", python_version="")
|
||||
)
|
||||
result = _run_check(app_client)
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
@@ -93,6 +93,7 @@ class TestGCPProvider:
|
||||
"max_unused_account_days": 180,
|
||||
"storage_min_retention_days": 90,
|
||||
"secretmanager_max_rotation_days": 90,
|
||||
"appengine_php_min_version": "8.2",
|
||||
"mig_min_zones": 2,
|
||||
"max_snapshot_age_days": 90,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from googleapiclient.errors import HttpError
|
||||
|
||||
from prowler.providers.gcp.services.appengine.appengine_service import AppEngine
|
||||
from tests.providers.gcp.gcp_fixtures import (
|
||||
GCP_PROJECT_ID,
|
||||
GCP_US_CENTER1_LOCATION,
|
||||
mock_is_api_active,
|
||||
set_mocked_gcp_provider,
|
||||
)
|
||||
|
||||
|
||||
def _make_appengine_client(versions, app_error=None):
|
||||
client = MagicMock()
|
||||
apps = client.apps()
|
||||
if app_error:
|
||||
apps.get().execute.side_effect = app_error
|
||||
else:
|
||||
apps.get().execute.return_value = {
|
||||
"id": GCP_PROJECT_ID,
|
||||
"locationId": GCP_US_CENTER1_LOCATION,
|
||||
}
|
||||
apps.services().list().execute.return_value = {"services": [{"id": "default"}]}
|
||||
apps.services().list_next.return_value = None
|
||||
apps.services().versions().list().execute.return_value = {"versions": versions}
|
||||
apps.services().versions().list_next.return_value = None
|
||||
return client
|
||||
|
||||
|
||||
def _patched_service(client):
|
||||
return (
|
||||
patch(
|
||||
"prowler.providers.gcp.lib.service.service.GCPService.__is_api_active__",
|
||||
new=mock_is_api_active,
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.gcp.lib.service.service.GCPService.__generate_client__",
|
||||
new=lambda *_, **__: client,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class TestAppEngineService:
|
||||
def test_get_versions(self):
|
||||
client = _make_appengine_client(
|
||||
[
|
||||
{
|
||||
"name": f"apps/{GCP_PROJECT_ID}/services/default/versions/v1",
|
||||
"id": "v1",
|
||||
"runtime": "php82",
|
||||
"env": "standard",
|
||||
"servingStatus": "SERVING",
|
||||
},
|
||||
{
|
||||
"id": "v2",
|
||||
"runtime": "php",
|
||||
"env": "flexible",
|
||||
"servingStatus": "STOPPED",
|
||||
},
|
||||
]
|
||||
)
|
||||
api_active, generate_client = _patched_service(client)
|
||||
with api_active, generate_client:
|
||||
appengine = AppEngine(set_mocked_gcp_provider(project_ids=[GCP_PROJECT_ID]))
|
||||
|
||||
assert len(appengine.versions) == 2
|
||||
first = appengine.versions[0]
|
||||
assert first.id == f"apps/{GCP_PROJECT_ID}/services/default/versions/v1"
|
||||
assert first.name == "default/v1"
|
||||
assert first.version_id == "v1"
|
||||
assert first.service == "default"
|
||||
assert first.project_id == GCP_PROJECT_ID
|
||||
assert first.location == GCP_US_CENTER1_LOCATION
|
||||
assert first.runtime == "php82"
|
||||
assert first.env == "standard"
|
||||
assert first.serving_status == "SERVING"
|
||||
second = appengine.versions[1]
|
||||
assert second.id == f"apps/{GCP_PROJECT_ID}/services/default/versions/v2"
|
||||
assert second.env == "flexible"
|
||||
assert second.serving_status == "STOPPED"
|
||||
|
||||
def test_project_without_application(self):
|
||||
response = MagicMock(status=404)
|
||||
client = _make_appengine_client([], app_error=HttpError(response, b"not found"))
|
||||
api_active, generate_client = _patched_service(client)
|
||||
with api_active, generate_client:
|
||||
appengine = AppEngine(set_mocked_gcp_provider(project_ids=[GCP_PROJECT_ID]))
|
||||
|
||||
assert appengine.versions == []
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
||||
from tests.providers.gcp.gcp_fixtures import (
|
||||
GCP_PROJECT_ID,
|
||||
GCP_US_CENTER1_LOCATION,
|
||||
set_mocked_gcp_provider,
|
||||
)
|
||||
|
||||
CHECK_PATH = "prowler.providers.gcp.services.appengine.appengine_version_php_runtime_supported.appengine_version_php_runtime_supported"
|
||||
|
||||
|
||||
def _version(runtime, env="standard", serving_status="SERVING"):
|
||||
from prowler.providers.gcp.services.appengine.appengine_service import Version
|
||||
|
||||
return Version(
|
||||
id=f"apps/{GCP_PROJECT_ID}/services/default/versions/v1",
|
||||
name="default/v1",
|
||||
version_id="v1",
|
||||
service="default",
|
||||
project_id=GCP_PROJECT_ID,
|
||||
location=GCP_US_CENTER1_LOCATION,
|
||||
runtime=runtime,
|
||||
env=env,
|
||||
serving_status=serving_status,
|
||||
)
|
||||
|
||||
|
||||
def _run_check(versions, audit_config=None):
|
||||
appengine_client = mock.MagicMock()
|
||||
appengine_client.versions = versions
|
||||
appengine_client.audit_config = audit_config or {}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_gcp_provider(),
|
||||
),
|
||||
mock.patch(f"{CHECK_PATH}.appengine_client", new=appengine_client),
|
||||
):
|
||||
from prowler.providers.gcp.services.appengine.appengine_version_php_runtime_supported.appengine_version_php_runtime_supported import (
|
||||
appengine_version_php_runtime_supported,
|
||||
)
|
||||
|
||||
return appengine_version_php_runtime_supported().execute()
|
||||
|
||||
|
||||
class Test_appengine_version_php_runtime_supported:
|
||||
def test_no_versions(self):
|
||||
assert len(_run_check([])) == 0
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"runtime,env,serving_status",
|
||||
[
|
||||
("python312", "standard", "SERVING"),
|
||||
("nodejs20", "standard", "SERVING"),
|
||||
("php82", "standard", "STOPPED"),
|
||||
("custom", "flexible", "SERVING"),
|
||||
],
|
||||
)
|
||||
def test_skipped_versions(self, runtime, env, serving_status):
|
||||
assert len(_run_check([_version(runtime, env, serving_status)])) == 0
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"runtime,expected_status,expected_message",
|
||||
[
|
||||
(
|
||||
"php82",
|
||||
"PASS",
|
||||
"App Engine version default/v1 runs PHP 8.2 ('php82'), a supported runtime that receives security fixes automatically.",
|
||||
),
|
||||
(
|
||||
"php85",
|
||||
"PASS",
|
||||
"App Engine version default/v1 runs PHP 8.5 ('php85'), a supported runtime that receives security fixes automatically.",
|
||||
),
|
||||
(
|
||||
"php81",
|
||||
"FAIL",
|
||||
"App Engine version default/v1 runs PHP 8.1 ('php81'), a runtime that no longer receives security fixes (minimum supported version is 8.2).",
|
||||
),
|
||||
(
|
||||
"php55",
|
||||
"FAIL",
|
||||
"App Engine version default/v1 runs PHP 5.5 ('php55'), a runtime that no longer receives security fixes (minimum supported version is 8.2).",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_standard_runtime(self, runtime, expected_status, expected_message):
|
||||
result = _run_check([_version(runtime)])
|
||||
assert len(result) == 1
|
||||
assert result[0].status == expected_status
|
||||
assert result[0].status_extended == expected_message
|
||||
assert (
|
||||
result[0].resource_id
|
||||
== f"apps/{GCP_PROJECT_ID}/services/default/versions/v1"
|
||||
)
|
||||
assert result[0].resource_name == "default/v1"
|
||||
assert result[0].project_id == GCP_PROJECT_ID
|
||||
assert result[0].location == GCP_US_CENTER1_LOCATION
|
||||
|
||||
def test_flexible_runtime_is_manual(self):
|
||||
result = _run_check([_version("php", env="flexible")])
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "MANUAL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "App Engine version default/v1 runs on the flexible environment with runtime 'php', which is not patched in place; redeploy it on a fixed PHP release (8.2 or newer) and verify the image."
|
||||
)
|
||||
|
||||
def test_custom_minimum(self):
|
||||
result = _run_check([_version("php83")], {"appengine_php_min_version": "8.4"})
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
Reference in new issue
Block a user