feat(app): Add new Azure functions checks (#4189)

Co-authored-by: Sergio <sergio@prowler.com>
This commit is contained in:
Rubén De la Torre Vico
2024-06-21 17:32:31 +02:00
committed by GitHub
parent 465261e1df
commit 536f0df9d3
38 changed files with 2090 additions and 42 deletions
+4 -2
View File
@@ -7,11 +7,13 @@ WINDOWS_AZURE_SERVICE_MANAGEMENT_API = "797f4846-ba00-4fd7-ba43-dac1f8f63013"
GUEST_USER_ACCESS_NO_RESTRICTICTED = UUID("a0b1b346-4d3e-4e8b-98f8-753987be4970")
GUEST_USER_ACCESS_RESTRICTICTED = UUID("2af84b1e-32c8-42b7-82bc-daa82404023b")
# General built-in roles
# General administrator built-in roles
CONTRIBUTOR_ROLE_ID = "b24988ac-6180-42a0-ab88-20f7382dd24c"
OWNER_ROLE_ID = "8e3af657-a8ff-443c-a75c-2fe8c4bcb635"
ROLE_BASED_ACCESS_CONTROL_ADMINISTRATOR_ROLE_ID = "f58310d9-a9f6-439a-9e8d-f62e7b41a168"
USER_ACCESS_ADMINISTRATOR_ROLE_ID = "18d7d88d-d35e-4fb5-a5c3-7773c20a72d9"
# Compute roles
# Compute admin roles IDs
VIRTUAL_MACHINE_CONTRIBUTOR_ROLE_ID = "9980e02c-c2be-4d73-94e8-173b1dc7cf3c"
VIRTUAL_MACHINE_ADMINISTRATOR_LOGIN_ROLE_ID = "1c0163c0-47e6-4577-8991-ea5c82e286e4"
VIRTUAL_MACHINE_USER_LOGIN_ROLE_ID = "fb879df8-f326-4884-b1cf-06f3ad86be52"
@@ -0,0 +1,30 @@
{
"Provider": "azure",
"CheckID": "app_function_access_keys_configured",
"CheckTitle": "Ensure that Azure Functions are using access keys for enhanced security",
"CheckType": [],
"ServiceName": "app",
"SubServiceName": "function",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "Microsoft.Web/sites",
"Description": "Azure Functions provide a way to secure HTTP function endpoints during development and production. Using access keys adds an extra layer of protection, ensuring that only authorized users or systems can access the functions. This is particularly important when dealing with public apps or sensitive data.",
"Risk": "Unprotected function endpoints may be vulnerable to unauthorized access, leading to potential data breaches or malicious activity.",
"RelatedUrl": "https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-http-webhook-trigger?tabs=python-v2%2Cisolated-process%2Cnodejs-v4%2Cfunctionsv2&pivots=programming-language-csharp#authorization-keys",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/azure/Functions/azure-function-anonymous-access.html",
"Terraform": ""
},
"Recommendation": {
"Text": "Use access keys to secure Azure Functions. You can create and manage keys in the Azure portal or using the Azure CLI. For more information, see the official documentation.",
"Url": "https://learn.microsoft.com/en-us/azure/azure-functions/security-concepts?tabs=v4#function-access-keys"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": "For additional security, consider using managed identities and key vaults along with access keys. This provides granular control over resource access and improves auditability."
}
@@ -0,0 +1,32 @@
from prowler.lib.check.models import Check, Check_Report_Azure
from prowler.providers.azure.services.app.app_client import app_client
class app_function_access_keys_configured(Check):
def execute(self):
findings = []
for (
subscription_name,
functions,
) in app_client.functions.items():
for function_id, function in functions.items():
report = Check_Report_Azure(self.metadata())
report.status = "FAIL"
report.status_extended = (
f"Function {function.name} does not have function keys configured."
)
report.subscription = subscription_name
report.resource_name = function.name
report.resource_id = function_id
report.location = function.location
if len(function.function_keys) > 0:
report.status = "PASS"
report.status_extended = (
f"Function {function.name} has function keys configured."
)
findings.append(report)
return findings
@@ -0,0 +1,30 @@
{
"Provider": "azure",
"CheckID": "app_function_application_insights_enabled",
"CheckTitle": "Ensure Function App has Application Insights configured",
"CheckType": [],
"ServiceName": "app",
"SubServiceName": "function",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "Microsoft.Web/sites",
"Description": "Application Insights is a powerful tool for monitoring the performance and health of Azure Function Apps. It provides valuable insights into exceptions, performance issues, and usage patterns, enabling timely detection and resolution of issues.",
"Risk": "Without Application Insights, you may miss critical errors, performance degradation, or abnormal behavior in your Function App, potentially impacting availability and user experience.",
"RelatedUrl": "https://learn.microsoft.com/en-us/azure/azure-monitor/app/app-insights-overview",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/azure/Functions/function-app-insights-on.html",
"Terraform": ""
},
"Recommendation": {
"Text": "Enable Application Insights for your Azure Function App to monitor its performance and health.",
"Url": "https://learn.microsoft.com/en-us/azure/azure-monitor/app/monitor-functions"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,42 @@
from prowler.lib.check.models import Check, Check_Report_Azure
from prowler.providers.azure.services.app.app_client import app_client
from prowler.providers.azure.services.appinsights.appinsights_client import (
appinsights_client,
)
class app_function_application_insights_enabled(Check):
def execute(self):
findings = []
for (
subscription_name,
functions,
) in app_client.functions.items():
for function_id, function in functions.items():
report = Check_Report_Azure(self.metadata())
report.status = "FAIL"
report.status_extended = (
f"Function {function.name} is not using Application Insights."
)
report.subscription = subscription_name
report.resource_name = function.name
report.resource_id = function_id
report.location = function.location
if function.enviroment_variables.get(
"APPINSIGHTS_INSTRUMENTATIONKEY", ""
) in [
component.instrumentation_key
for component in appinsights_client.components[
subscription_name
].values()
]:
report.status = "PASS"
report.status_extended = (
f"Function {function.name} is using Application Insights."
)
findings.append(report)
return findings
@@ -0,0 +1,31 @@
{
"Provider": "azure",
"CheckID": "app_function_ftps_deployment_disabled",
"CheckTitle": "Ensure that FTP and FTPS deployments are disabled for Azure Functions to prevent unauthorized access and data breaches.",
"CheckType": [],
"ServiceName": "app",
"SubServiceName": "function",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "Microsoft.Web/sites",
"Description": "Azure FTP deployment endpoints are unencrypted and public, making them vulnerable to attacks. Disabling FTP and FTPS deployments enhances security by preventing unauthorized access to login credentials and sensitive codebases.",
"Risk": "If left enabled, attackers can intercept network traffic and gain full control of the app or service, leading to potential data breaches and unauthorized modifications.",
"RelatedUrl": "https://docs.microsoft.com/en-us/azure/app-service/deploy-ftp",
"Remediation": {
"Code": {
"CLI": "az webapp config set --resource-group <resource-group> --name <app-name> --ftps-state Disabled",
"NativeIaC": "",
"Other": "",
"Terraform": "",
"Arm": ""
},
"Recommendation": {
"Text": "It is recommended to disable FTP and FTPS deployments for Azure Functions to mitigate security risks. Instead, consider using more secure deployment methods such as Docker contianer or enabling continuous deployment with GitHub Actions.",
"Url": "https://learn.microsoft.com/en-us/azure/azure-functions/functions-deployment-technologies?tabs=windows#trigger-syncing"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": "This check ensures that Azure Functions are deployed securely, reducing the attack surface and protecting sensitive information."
}
@@ -0,0 +1,30 @@
from prowler.lib.check.models import Check, Check_Report_Azure
from prowler.providers.azure.services.app.app_client import app_client
class app_function_ftps_deployment_disabled(Check):
def execute(self) -> Check_Report_Azure:
findings = []
for (
subscription_name,
functions,
) in app_client.functions.items():
for function_id, function in functions.items():
report = Check_Report_Azure(self.metadata())
report.status = "FAIL"
report.status_extended = f"Function {function.name} has {'FTP' if function.ftps_state == 'AllAllowed' else 'FTPS' if function.ftps_state == 'FtpsOnly' else 'FTP or FTPS'} deployment enabled"
report.subscription = subscription_name
report.resource_name = function.name
report.resource_id = function_id
report.location = function.location
if function.ftps_state == "Disabled":
report.status = "PASS"
report.status_extended = (
f"Function {function.name} has FTP and FTPS deployment disabled"
)
findings.append(report)
return findings
@@ -0,0 +1,30 @@
{
"Provider": "azure",
"CheckID": "app_function_identity_is_configured",
"CheckTitle": "Ensure Azure function has system or user assigned managed identity configured",
"CheckType": [],
"ServiceName": "app",
"SubServiceName": "function",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "Microsoft.Web/sites",
"Description": "Azure Functions should have managed identities configured for enhanced security and access control.",
"Risk": "Not using managed identities can lead to less secure authentication and authorization practices, potentially exposing sensitive data.",
"RelatedUrl": "https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/overview",
"Remediation": {
"Code": {
"CLI": "az functionapp identity assign --name <function_name> --resource-group <resource_group> --identities [system]",
"NativeIaC": "",
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/azure/Functions/azure-function-system-assigned-identity.html",
"Terraform": ""
},
"Recommendation": {
"Text": "It is recommended to enable managed identities for Azure Functions to enhance security and access control. This allows the function app to easily access other Azure resources securely and with the appropriate permissions.",
"Url": "https://learn.microsoft.com/en-us/azure/app-service/overview-managed-identity"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,28 @@
from prowler.lib.check.models import Check, Check_Report_Azure
from prowler.providers.azure.services.app.app_client import app_client
class app_function_identity_is_configured(Check):
def execute(self):
findings = []
for (
subscription_name,
functions,
) in app_client.functions.items():
for function_id, function in functions.items():
report = Check_Report_Azure(self.metadata())
report.status = "FAIL"
report.status_extended = f"Function {function.name} does not have a managed identity enabled."
report.subscription = subscription_name
report.resource_name = function.name
report.resource_id = function_id
report.location = function.location
if function.identity:
report.status = "PASS"
report.status_extended = f"Function {function.name} has a {function.identity.type if getattr(function.identity, 'type', '') else 'managed'} identity enabled."
findings.append(report)
return findings
@@ -0,0 +1,30 @@
{
"Provider": "azure",
"CheckID": "app_function_identity_without_admin_privileges",
"CheckTitle": "Ensure that your Azure functions are not configured with an identity with admin privileges",
"CheckType": [],
"ServiceName": "app",
"SubServiceName": "function",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "Microsoft.Web/sites",
"Description": "It is important to ensure that Azure functions are not configured with administrative privileges to maintain the principle of least privilege and reduce the attack surface. By limiting the privileges of Azure functions, potential security risks and data leaks can be mitigated.",
"Risk": "If Azure functions are configured with administrative privileges, it increases the risk of unauthorized access, privilege escalation, and data breaches. Attackers can exploit these privileges to gain access to sensitive data and compromise the entire system.",
"RelatedUrl": "https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/overview",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/azure/Functions/azure-function-admin-permissions.html",
"Terraform": ""
},
"Recommendation": {
"Text": "To remediate this issue, ensure that Azure functions are not configured with an identity that has administrative privileges. Instead, use the principle of least privilege to grant only the necessary permissions to Azure functions. For more information, refer to the official documentation: Use the principle of least privilege.",
"Url": "https://docs.microsoft.com/en-us/azure/architecture/framework/security/design-identity-authorization#use-the-principle-of-least-privilege"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": "This check helps prevent privilege escalation attacks and ensures that Azure functions operate with the necessary permissions, reducing the impact of potential security breaches."
}
@@ -0,0 +1,55 @@
from prowler.lib.check.models import Check, Check_Report_Azure
from prowler.providers.azure.config import (
CONTRIBUTOR_ROLE_ID,
OWNER_ROLE_ID,
ROLE_BASED_ACCESS_CONTROL_ADMINISTRATOR_ROLE_ID,
USER_ACCESS_ADMINISTRATOR_ROLE_ID,
)
from prowler.providers.azure.services.app.app_client import app_client
from prowler.providers.azure.services.iam.iam_client import iam_client
class app_function_identity_without_admin_privileges(Check):
def execute(self):
findings = []
for (
subscription_name,
functions,
) in app_client.functions.items():
for function_id, function in functions.items():
if function.identity:
report = Check_Report_Azure(self.metadata())
report.status = "PASS"
report.status_extended = f"Function {function.name} has a managed identity enabled but without admin privileges."
report.subscription = subscription_name
report.resource_name = function.name
report.resource_id = function_id
report.location = function.location
admin_roles_assigned = []
for role_assignment in iam_client.role_assignments[
subscription_name
].values():
if (
role_assignment.agent_id == function.identity.principal_id
and role_assignment.role_id
in [
CONTRIBUTOR_ROLE_ID,
OWNER_ROLE_ID,
ROLE_BASED_ACCESS_CONTROL_ADMINISTRATOR_ROLE_ID,
USER_ACCESS_ADMINISTRATOR_ROLE_ID,
]
):
for role in iam_client.roles[subscription_name]:
if role.id.split("/")[-1] == role_assignment.role_id:
admin_roles_assigned.append(role.name)
if admin_roles_assigned:
report.status = "FAIL"
report.status_extended = f"Function {function.name} has a managed identity enabled and it is configure with admin privileges using {'roles: ' + ', '.join(admin_roles_assigned) if len(admin_roles_assigned) > 1 else 'role ' + admin_roles_assigned[0]}."
findings.append(report)
return findings
@@ -0,0 +1,30 @@
{
"Provider": "azure",
"CheckID": "app_function_latest_runtime_version",
"CheckTitle": "Ensure Azure Functions are using the latest supported runtime",
"CheckType": [],
"ServiceName": "app",
"SubServiceName": "function",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "Microsoft.Web/sites",
"Description": "Keeping Azure Functions up to date with the latest supported runtime version is crucial for security and performance. Updates often include security patches and enhancements, helping to protect against known vulnerabilities and potential exploits. Additionally, newer runtime versions may offer improved functionality and optimized resource utilization.",
"Risk": "Using outdated runtime versions may introduce security risks and performance degradation. Outdated runtimes may have unpatched vulnerabilities, making them susceptible to attacks.",
"RelatedUrl": "https://docs.microsoft.com/en-us/azure/azure-functions/functions-versions",
"Remediation": {
"Code": {
"CLI": "az functionapp config appsettings set --name <function_app_name> --resource-group <resource_group_name> --settings FUNCTIONS_EXTENSION_VERSION=~4",
"NativeIaC": "",
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/azure/Functions/azure-function-runtime-version.html",
"Terraform": ""
},
"Recommendation": {
"Text": "",
"Url": "https://learn.microsoft.com/en-us/azure/azure-functions/migrate-version-3-version-4?tabs=net8%2Cazure-cli%2Cwindows&pivots=programming-language-python"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": "Stay informed about the latest security updates and patch releases for Azure Functions to maintain a secure and up-to-date environment."
}
@@ -0,0 +1,33 @@
from prowler.lib.check.models import Check, Check_Report_Azure
from prowler.providers.azure.services.app.app_client import app_client
class app_function_latest_runtime_version(Check):
def execute(self):
findings = []
for (
subscription_name,
functions,
) in app_client.functions.items():
for function_id, function in functions.items():
report = Check_Report_Azure(self.metadata())
report.status = "PASS"
report.status_extended = (
f"Function {function.name} is using the latest runtime."
)
report.subscription = subscription_name
report.resource_name = function.name
report.resource_id = function_id
report.location = function.location
if (
function.enviroment_variables.get("FUNCTIONS_EXTENSION_VERSION", "")
!= "~4"
):
report.status = "FAIL"
report.status_extended = f"Function {function.name} is not using the latest runtime. The current runtime is '{function.enviroment_variables.get('FUNCTIONS_EXTENSION_VERSION', '')}' and should be '~4'."
findings.append(report)
return findings
@@ -0,0 +1,30 @@
{
"Provider": "azure",
"CheckID": "app_function_not_publicly_accessible",
"CheckTitle": "Ensure Azure Functions are not publicly accessible",
"CheckType": [],
"ServiceName": "app",
"SubServiceName": "function",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "Microsoft.Web/sites",
"Description": "Azure Functions should not be exposed to the public internet. Restricting access helps protect applications from potential threats and reduces the attack surface.",
"Risk": "Exposing Azure Functions to the public internet increases the risk of unauthorized access, data breaches, and other security threats.",
"RelatedUrl": "https://learn.microsoft.com/en-us/azure/azure-functions/functions-networking-options",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/azure/Functions/azure-function-exposed.html",
"Terraform": ""
},
"Recommendation": {
"Text": "Review the Azure Functions security guidelines and ensure that access restrictions are in place. Use Azure Private Link and Key Vault for enhanced security.",
"Url": "https://learn.microsoft.com/en-us/azure/app-service/overview-access-restrictions"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,32 @@
from prowler.lib.check.models import Check, Check_Report_Azure
from prowler.providers.azure.services.app.app_client import app_client
class app_function_not_publicly_accessible(Check):
def execute(self):
findings = []
for (
subscription_name,
functions,
) in app_client.functions.items():
for function_id, function in functions.items():
report = Check_Report_Azure(self.metadata())
report.status = "FAIL"
report.status_extended = (
f"Function {function.name} is publicly accessible."
)
report.subscription = subscription_name
report.resource_name = function.name
report.resource_id = function_id
report.location = function.location
if not function.public_access:
report.status = "PASS"
report.status_extended = (
f"Function {function.name} is not publicly accessible."
)
findings.append(report)
return findings
@@ -0,0 +1,30 @@
{
"Provider": "azure",
"CheckID": "app_function_vnet_integration_enabled",
"CheckTitle": "Ensure Virtual Network Integration is Enabled for Azure Functions",
"CheckType": [],
"ServiceName": "app",
"SubServiceName": "function",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "Microsoft.Web/sites",
"Description": "Enabling Virtual Network Integration for Azure Functions provides an additional layer of security by restricting access to selected virtual network subnets. This helps to protect your Function Apps from unauthorized access and potential threats.",
"Risk": "Without Virtual Network Integration, your Function Apps may be exposed to the public internet, increasing the risk of unauthorized access and potential security breaches.",
"RelatedUrl": "https://docs.microsoft.com/en-us/azure/azure-functions/functions-networking-options#virtual-network-integration",
"Remediation": {
"Code": {
"CLI": "az functionapp vnet-integration update --name <function_app_name> --resource-group <resource_group_name> --vnet <vnet_name> --subnet <subnet_name>",
"NativeIaC": "",
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/azure/Functions/azure-function-vnet-integration-on.html",
"Terraform": ""
},
"Recommendation": {
"Text": "It is recommended to enable Virtual Network Integration for Azure Functions to enhance security and protect against unauthorized access.",
"Url": "https://docs.microsoft.com/en-us/azure/azure-functions/functions-networking-options#enable-virtual-network-integration"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,28 @@
from prowler.lib.check.models import Check, Check_Report_Azure
from prowler.providers.azure.services.app.app_client import app_client
class app_function_vnet_integration_enabled(Check):
def execute(self):
findings = []
for (
subscription_name,
functions,
) in app_client.functions.items():
for function_id, function in functions.items():
report = Check_Report_Azure(self.metadata())
report.status = "FAIL"
report.status_extended = f"Function {function.name} does not have virtual network integration enabled."
report.subscription = subscription_name
report.resource_name = function.name
report.resource_id = function_id
report.location = function.location
if function.vnet_subnet_id:
report.status = "PASS"
report.status_extended = f"Function {function.name} has Virtual Network integration enabled with subnet '{function.vnet_subnet_id}' enabled."
findings.append(report)
return findings
@@ -1,4 +1,5 @@
from dataclasses import dataclass
from typing import Dict
from azure.mgmt.web import WebSiteManagementClient
from azure.mgmt.web.models import ManagedServiceIdentity, SiteConfigResource
@@ -10,11 +11,11 @@ from prowler.providers.azure.services.monitor.monitor_client import monitor_clie
from prowler.providers.azure.services.monitor.monitor_service import DiagnosticSetting
########################## App
class App(AzureService):
def __init__(self, provider: AzureProvider):
super().__init__(WebSiteManagementClient, provider)
self.apps = self.__get_apps__()
self.functions = self.__get_functions__()
def __get_apps__(self):
logger.info("App - Getting apps...")
@@ -26,41 +27,43 @@ class App(AzureService):
apps.update({subscription_name: {}})
for app in apps_list:
platform_auth = getattr(
client.web_apps.get_auth_settings_v2(
resource_group_name=app.resource_group, name=app.name
),
"platform",
None,
)
# Filter function apps
if getattr(app, "kind", "app").startswith("app"):
platform_auth = getattr(
client.web_apps.get_auth_settings_v2(
resource_group_name=app.resource_group, name=app.name
),
"platform",
None,
)
apps[subscription_name].update(
{
app.name: WebApp(
resource_id=app.id,
auth_enabled=(
getattr(platform_auth, "enabled", False)
if platform_auth
else False
),
configurations=client.web_apps.get_configuration(
resource_group_name=app.resource_group,
name=app.name,
),
client_cert_mode=self.__get_client_cert_mode__(
getattr(app, "client_cert_enabled", False),
getattr(app, "client_cert_mode", "Ignore"),
),
monitor_diagnostic_settings=self.__get_app_monitor_settings__(
app.name, app.resource_group, subscription_name
),
https_only=getattr(app, "https_only", False),
identity=getattr(app, "identity", None),
location=app.location,
kind=getattr(app, "kind", "app"),
)
}
)
apps[subscription_name].update(
{
app.name: WebApp(
resource_id=app.id,
auth_enabled=(
getattr(platform_auth, "enabled", False)
if platform_auth
else False
),
configurations=client.web_apps.get_configuration(
resource_group_name=app.resource_group,
name=app.name,
),
client_cert_mode=self.__get_client_cert_mode__(
getattr(app, "client_cert_enabled", False),
getattr(app, "client_cert_mode", "Ignore"),
),
monitor_diagnostic_settings=self.__get_app_monitor_settings__(
app.name, app.resource_group, subscription_name
),
https_only=getattr(app, "https_only", False),
identity=getattr(app, "identity", None),
location=app.location,
kind=app.kind,
)
}
)
except Exception as error:
logger.error(
f"Subscription name: {subscription_name} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
@@ -68,6 +71,73 @@ class App(AzureService):
return apps
def __get_functions__(self):
logger.info("Function - Getting functions...")
functions = {}
for subscription_name, client in self.clients.items():
try:
functions_list = client.web_apps.list()
functions.update({subscription_name: {}})
for function in functions_list:
# Filter function apps
if getattr(function, "kind", "").startswith("functionapp"):
# List host keys
host_keys = client.web_apps.list_host_keys(
resource_group_name=function.resource_group,
name=function.name,
) # Need to add role 'Logic App Contributor' to the service principal to get the host keys or add to the reader role the permission 'Microsoft.Web/sites/host/listkeys'
function_config = client.web_apps.get_configuration(
resource_group_name=function.resource_group,
name=function.name,
)
functions[subscription_name].update(
{
function.id: FunctionApp(
name=function.name,
location=function.location,
kind=function.kind,
function_keys=getattr(
host_keys, "function_keys", {}
),
enviroment_variables=getattr(
client.web_apps.list_application_settings(
resource_group_name=function.resource_group,
name=function.name,
),
"properties",
{},
),
identity=getattr(function, "identity", None),
public_access=(
False
if getattr(
function, "public_network_access", ""
)
== "Disabled"
else True
),
vnet_subnet_id=getattr(
function,
"virtual_network_subnet_id",
"",
),
ftps_state=getattr(
function_config, "ftps_state", ""
),
)
}
)
except Exception as error:
logger.error(
f"Subscription name: {subscription_name} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
return functions
def __get_client_cert_mode__(
self, client_cert_enabled: bool, client_cert_mode: str
):
@@ -112,3 +182,16 @@ class WebApp:
https_only: bool = False
monitor_diagnostic_settings: list[DiagnosticSetting] = None
kind: str = "app"
@dataclass
class FunctionApp:
name: str
location: str
kind: str
function_keys: Dict[str, str]
enviroment_variables: Dict[str, str]
identity: ManagedServiceIdentity
public_access: bool
vnet_subnet_id: str
ftps_state: str
@@ -1,6 +1,5 @@
from dataclasses import dataclass
from azure.mgmt.applicationinsights import ApplicationInsightsManagementClient
from pydantic import BaseModel
from prowler.lib.logger import logger
from prowler.providers.azure.azure_provider import AzureProvider
@@ -29,6 +28,9 @@ class AppInsights(AzureService):
resource_id=component.id,
resource_name=component.name,
location=component.location,
instrumentation_key=getattr(
component, "instrumentation_key", "Not Found"
),
)
}
)
@@ -40,8 +42,8 @@ class AppInsights(AzureService):
return components
@dataclass
class Component:
class Component(BaseModel):
resource_id: str
resource_name: str
location: str
instrumentation_key: str
@@ -0,0 +1,147 @@
from unittest import mock
from uuid import uuid4
from tests.providers.azure.azure_fixtures import (
AZURE_SUBSCRIPTION_ID,
set_mocked_azure_provider,
)
class Test_app_function_access_keys_configured:
def test_app_no_subscriptions(self):
app_client = mock.MagicMock
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_function_access_keys_configured.app_function_access_keys_configured.app_client",
new=app_client,
):
from prowler.providers.azure.services.app.app_function_access_keys_configured.app_function_access_keys_configured import (
app_function_access_keys_configured,
)
app_client.functions = {}
check = app_function_access_keys_configured()
result = check.execute()
assert len(result) == 0
def test_app_subscription_empty(self):
app_client = mock.MagicMock
app_client.functions = {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_function_access_keys_configured.app_function_access_keys_configured.app_client",
new=app_client,
):
from prowler.providers.azure.services.app.app_function_access_keys_configured.app_function_access_keys_configured import (
app_function_access_keys_configured,
)
check = app_function_access_keys_configured()
result = check.execute()
assert len(result) == 0
def test_app_function_no_keys(self):
app_client = mock.MagicMock
app_client.functions = {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_function_access_keys_configured.app_function_access_keys_configured.app_client",
new=app_client,
):
from prowler.providers.azure.services.app.app_function_access_keys_configured.app_function_access_keys_configured import (
app_function_access_keys_configured,
)
from prowler.providers.azure.services.app.app_service import FunctionApp
function_id = str(uuid4())
app_client.functions = {
AZURE_SUBSCRIPTION_ID: {
function_id: FunctionApp(
name="function1",
location="West Europe",
kind="functionapp,linux",
function_keys={},
enviroment_variables={},
identity=None,
public_access=False,
vnet_subnet_id=None,
ftps_state="AllAllowed",
)
}
}
check = app_function_access_keys_configured()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "Function function1 does not have function keys configured."
)
assert result[0].resource_id == function_id
assert result[0].resource_name == "function1"
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
assert result[0].location == "West Europe"
def test_app_function_using_functions_keys(self):
app_client = mock.MagicMock
app_client.functions = {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_function_access_keys_configured.app_function_access_keys_configured.app_client",
new=app_client,
):
from prowler.providers.azure.services.app.app_function_access_keys_configured.app_function_access_keys_configured import (
app_function_access_keys_configured,
)
from prowler.providers.azure.services.app.app_service import FunctionApp
function_id = str(uuid4())
app_client.functions = {
AZURE_SUBSCRIPTION_ID: {
function_id: FunctionApp(
name="function1",
location="West Europe",
kind="functionapp,linux",
function_keys={
"default": "key1",
"key2": "key2",
},
enviroment_variables={},
identity=None,
public_access=False,
vnet_subnet_id=None,
ftps_state="AllAllowed",
)
}
}
check = app_function_access_keys_configured()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "Function function1 has function keys configured."
)
assert result[0].resource_id == function_id
assert result[0].resource_name == "function1"
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
assert result[0].location == "West Europe"
@@ -0,0 +1,305 @@
from unittest import mock
from uuid import uuid4
from tests.providers.azure.azure_fixtures import (
AZURE_SUBSCRIPTION_ID,
set_mocked_azure_provider,
)
class Test_app_function_application_insights_enabled:
def test_app_no_subscriptions(self):
app_client = mock.MagicMock
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_function_application_insights_enabled.app_function_application_insights_enabled.app_client",
new=app_client,
):
from prowler.providers.azure.services.app.app_function_application_insights_enabled.app_function_application_insights_enabled import (
app_function_application_insights_enabled,
)
app_client.functions = {}
check = app_function_application_insights_enabled()
result = check.execute()
assert len(result) == 0
def test_app_subscription_empty(self):
app_client = mock.MagicMock
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_function_application_insights_enabled.app_function_application_insights_enabled.app_client",
new=app_client,
):
from prowler.providers.azure.services.app.app_function_application_insights_enabled.app_function_application_insights_enabled import (
app_function_application_insights_enabled,
)
app_client.functions = {AZURE_SUBSCRIPTION_ID: {}}
check = app_function_application_insights_enabled()
result = check.execute()
assert len(result) == 0
def test_app_function_no_app_insights(self):
app_client = mock.MagicMock
app_insights = mock.MagicMock
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_function_application_insights_enabled.app_function_application_insights_enabled.app_client",
new=app_client,
), mock.patch(
"prowler.providers.azure.services.app.app_function_application_insights_enabled.app_function_application_insights_enabled.appinsights_client",
new=app_insights,
):
from prowler.providers.azure.services.app.app_function_application_insights_enabled.app_function_application_insights_enabled import (
app_function_application_insights_enabled,
)
from prowler.providers.azure.services.app.app_service import FunctionApp
from prowler.providers.azure.services.appinsights.appinsights_service import (
Component,
)
function_id = str(uuid4())
app_client.functions = {
AZURE_SUBSCRIPTION_ID: {
function_id: FunctionApp(
name="function1",
location="West Europe",
kind="functionapp,linux",
function_keys={},
enviroment_variables={},
identity=None,
public_access=False,
vnet_subnet_id=None,
ftps_state="AllAllowed",
)
}
}
app_insights.components = {
AZURE_SUBSCRIPTION_ID: {
"app_id-1": Component(
resource_id="component_id",
resource_name="component_name",
location="West Europe",
instrumentation_key="1234",
)
}
}
check = app_function_application_insights_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "Function function1 is not using Application Insights."
)
assert result[0].resource_id == function_id
assert result[0].resource_name == "function1"
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
assert result[0].location == "West Europe"
def test_app_function_using_app_insights(self):
app_client = mock.MagicMock
app_insights = mock.MagicMock
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_function_application_insights_enabled.app_function_application_insights_enabled.app_client",
new=app_client,
), mock.patch(
"prowler.providers.azure.services.app.app_function_application_insights_enabled.app_function_application_insights_enabled.appinsights_client",
new=app_insights,
):
from prowler.providers.azure.services.app.app_function_application_insights_enabled.app_function_application_insights_enabled import (
app_function_application_insights_enabled,
)
from prowler.providers.azure.services.app.app_service import FunctionApp
from prowler.providers.azure.services.appinsights.appinsights_service import (
Component,
)
function_id = str(uuid4())
app_client.functions = {
AZURE_SUBSCRIPTION_ID: {
function_id: FunctionApp(
name="function1",
location="West Europe",
kind="functionapp,linux",
function_keys={},
enviroment_variables={"APPINSIGHTS_INSTRUMENTATIONKEY": "1234"},
identity=None,
public_access=False,
vnet_subnet_id=None,
ftps_state="AllAllowed",
)
}
}
app_insights.components = {
AZURE_SUBSCRIPTION_ID: {
"app_id-1": Component(
resource_id="component_id",
resource_name="component_name",
location="West Europe",
instrumentation_key="1234",
)
}
}
check = app_function_application_insights_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "Function function1 is using Application Insights."
)
assert result[0].resource_id == function_id
assert result[0].resource_name == "function1"
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
assert result[0].location == "West Europe"
def test_app_function_using_app_insights_different_key(self):
app_client = mock.MagicMock
app_insights = mock.MagicMock
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_function_application_insights_enabled.app_function_application_insights_enabled.app_client",
new=app_client,
), mock.patch(
"prowler.providers.azure.services.app.app_function_application_insights_enabled.app_function_application_insights_enabled.appinsights_client",
new=app_insights,
):
from prowler.providers.azure.services.app.app_function_application_insights_enabled.app_function_application_insights_enabled import (
app_function_application_insights_enabled,
)
from prowler.providers.azure.services.app.app_service import FunctionApp
from prowler.providers.azure.services.appinsights.appinsights_service import (
Component,
)
function_id = str(uuid4())
app_client.functions = {
AZURE_SUBSCRIPTION_ID: {
function_id: FunctionApp(
name="function1",
location="West Europe",
kind="functionapp,linux",
function_keys={},
enviroment_variables={"APPINSIGHTS_INSTRUMENTATIONKEY": "1234"},
identity=None,
public_access=False,
vnet_subnet_id=None,
ftps_state="AllAllowed",
)
}
}
app_insights.components = {
AZURE_SUBSCRIPTION_ID: {
"app_id-1": Component(
resource_id="component_id",
resource_name="component_name",
location="West Europe",
instrumentation_key="5678",
)
}
}
check = app_function_application_insights_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "Function function1 is not using Application Insights."
)
assert result[0].resource_id == function_id
assert result[0].resource_name == "function1"
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
assert result[0].location == "West Europe"
def test_app_function_with_app_insights_no_key(self):
app_client = mock.MagicMock
app_insights = mock.MagicMock
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_function_application_insights_enabled.app_function_application_insights_enabled.app_client",
new=app_client,
), mock.patch(
"prowler.providers.azure.services.app.app_function_application_insights_enabled.app_function_application_insights_enabled.appinsights_client",
new=app_insights,
):
from prowler.providers.azure.services.app.app_function_application_insights_enabled.app_function_application_insights_enabled import (
app_function_application_insights_enabled,
)
from prowler.providers.azure.services.app.app_service import FunctionApp
from prowler.providers.azure.services.appinsights.appinsights_service import (
Component,
)
function_id = str(uuid4())
app_client.functions = {
AZURE_SUBSCRIPTION_ID: {
function_id: FunctionApp(
name="function1",
location="West Europe",
kind="functionapp,linux",
function_keys={},
enviroment_variables={},
identity=None,
public_access=False,
vnet_subnet_id=None,
ftps_state="AllAllowed",
)
}
}
app_insights.components = {
AZURE_SUBSCRIPTION_ID: {
"app_id-1": Component(
resource_id="component_id",
resource_name="component_name",
location="West Europe",
instrumentation_key="Not Found",
)
}
}
check = app_function_application_insights_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "Function function1 is not using Application Insights."
)
assert result[0].resource_id == function_id
assert result[0].resource_name == "function1"
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
assert result[0].location == "West Europe"
@@ -0,0 +1,187 @@
from unittest import mock
from uuid import uuid4
from tests.providers.azure.azure_fixtures import (
AZURE_SUBSCRIPTION_ID,
set_mocked_azure_provider,
)
class Test_app_function_ftps_deployment_disabled:
def test_no_subscriptions(self):
app_client = mock.MagicMock
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_function_ftps_deployment_disabled.app_function_ftps_deployment_disabled.app_client",
new=app_client,
):
from prowler.providers.azure.services.app.app_function_ftps_deployment_disabled.app_function_ftps_deployment_disabled import (
app_function_ftps_deployment_disabled,
)
app_client.functions = {}
check = app_function_ftps_deployment_disabled()
result = check.execute()
assert len(result) == 0
def test_subscription_empty(self):
app_client = mock.MagicMock
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_function_ftps_deployment_disabled.app_function_ftps_deployment_disabled.app_client",
new=app_client,
):
from prowler.providers.azure.services.app.app_function_ftps_deployment_disabled.app_function_ftps_deployment_disabled import (
app_function_ftps_deployment_disabled,
)
app_client.functions = {AZURE_SUBSCRIPTION_ID: {}}
check = app_function_ftps_deployment_disabled()
result = check.execute()
assert len(result) == 0
def test_function_ftp_deployment_enabled(self):
app_client = mock.MagicMock
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_function_ftps_deployment_disabled.app_function_ftps_deployment_disabled.app_client",
new=app_client,
):
from prowler.providers.azure.services.app.app_function_ftps_deployment_disabled.app_function_ftps_deployment_disabled import (
app_function_ftps_deployment_disabled,
)
from prowler.providers.azure.services.app.app_service import FunctionApp
function_id = str(uuid4())
app_client.functions = {
AZURE_SUBSCRIPTION_ID: {
function_id: FunctionApp(
name="function1",
location="West Europe",
kind="functionapp,linux",
function_keys={},
enviroment_variables={},
identity=mock.MagicMock(type="SystemAssigned"),
public_access=False,
vnet_subnet_id=None,
ftps_state="AllAllowed",
)
}
}
check = app_function_ftps_deployment_disabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "Function function1 has FTP deployment enabled"
)
assert result[0].resource_name == "function1"
assert result[0].resource_id == function_id
assert result[0].location == "West Europe"
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
def test_function_ftps_deployment_enabled(self):
app_client = mock.MagicMock
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_function_ftps_deployment_disabled.app_function_ftps_deployment_disabled.app_client",
new=app_client,
):
from prowler.providers.azure.services.app.app_function_ftps_deployment_disabled.app_function_ftps_deployment_disabled import (
app_function_ftps_deployment_disabled,
)
from prowler.providers.azure.services.app.app_service import FunctionApp
function_id = str(uuid4())
app_client.functions = {
AZURE_SUBSCRIPTION_ID: {
function_id: FunctionApp(
name="function1",
location="West Europe",
kind="functionapp,linux",
function_keys={},
enviroment_variables={},
identity=mock.MagicMock(type="SystemAssigned"),
public_access=False,
vnet_subnet_id=None,
ftps_state="FtpsOnly",
)
}
}
check = app_function_ftps_deployment_disabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "Function function1 has FTPS deployment enabled"
)
assert result[0].resource_name == "function1"
assert result[0].resource_id == function_id
assert result[0].location == "West Europe"
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
def test_function_ftp_and_ftps_deployment_disabled(self):
app_client = mock.MagicMock
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_function_ftps_deployment_disabled.app_function_ftps_deployment_disabled.app_client",
new=app_client,
):
from prowler.providers.azure.services.app.app_function_ftps_deployment_disabled.app_function_ftps_deployment_disabled import (
app_function_ftps_deployment_disabled,
)
from prowler.providers.azure.services.app.app_service import FunctionApp
function_id = str(uuid4())
app_client.functions = {
AZURE_SUBSCRIPTION_ID: {
function_id: FunctionApp(
name="function1",
location="West Europe",
kind="functionapp,linux",
function_keys={},
enviroment_variables={},
identity=mock.MagicMock(type="SystemAssigned"),
public_access=False,
vnet_subnet_id=None,
ftps_state="Disabled",
)
}
}
check = app_function_ftps_deployment_disabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "Function function1 has FTP and FTPS deployment disabled"
)
assert result[0].resource_name == "function1"
assert result[0].resource_id == function_id
assert result[0].location == "West Europe"
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
@@ -0,0 +1,141 @@
from unittest import mock
from uuid import uuid4
from tests.providers.azure.azure_fixtures import (
AZURE_SUBSCRIPTION_ID,
set_mocked_azure_provider,
)
class Test_app_function_identity_is_configured:
def test_app_no_subscriptions(self):
app_client = mock.MagicMock
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_function_identity_is_configured.app_function_identity_is_configured.app_client",
new=app_client,
):
from prowler.providers.azure.services.app.app_function_identity_is_configured.app_function_identity_is_configured import (
app_function_identity_is_configured,
)
app_client.functions = {}
check = app_function_identity_is_configured()
result = check.execute()
assert len(result) == 0
def test_app_subscription_empty(self):
app_client = mock.MagicMock
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_function_identity_is_configured.app_function_identity_is_configured.app_client",
new=app_client,
):
from prowler.providers.azure.services.app.app_function_identity_is_configured.app_function_identity_is_configured import (
app_function_identity_is_configured,
)
app_client.functions = {AZURE_SUBSCRIPTION_ID: {}}
check = app_function_identity_is_configured()
result = check.execute()
assert len(result) == 0
def test_app_function_no_identity(self):
app_client = mock.MagicMock
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_function_identity_is_configured.app_function_identity_is_configured.app_client",
new=app_client,
):
from prowler.providers.azure.services.app.app_function_identity_is_configured.app_function_identity_is_configured import (
app_function_identity_is_configured,
)
from prowler.providers.azure.services.app.app_service import FunctionApp
function_id = str(uuid4())
app_client.functions = {
AZURE_SUBSCRIPTION_ID: {
function_id: FunctionApp(
name="function1",
location="West Europe",
kind="functionapp,linux",
function_keys={},
enviroment_variables={},
identity=None,
public_access=False,
vnet_subnet_id=None,
ftps_state="AllAllowed",
)
}
}
check = app_function_identity_is_configured()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "Function function1 does not have a managed identity enabled."
)
assert result[0].resource_name == "function1"
assert result[0].resource_id == function_id
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
assert result[0].location == "West Europe"
def test_app_function_identity_configured(self):
app_client = mock.MagicMock
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_function_identity_is_configured.app_function_identity_is_configured.app_client",
new=app_client,
):
from prowler.providers.azure.services.app.app_function_identity_is_configured.app_function_identity_is_configured import (
app_function_identity_is_configured,
)
from prowler.providers.azure.services.app.app_service import FunctionApp
function_id = str(uuid4())
app_client.functions = {
AZURE_SUBSCRIPTION_ID: {
function_id: FunctionApp(
name="function1",
location="West Europe",
kind="functionapp,linux",
function_keys={},
enviroment_variables={},
identity=mock.MagicMock(type="SystemAssigned"),
public_access=False,
vnet_subnet_id=None,
ftps_state="AllAllowed",
)
}
}
check = app_function_identity_is_configured()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "Function function1 has a SystemAssigned identity enabled."
)
assert result[0].resource_name == "function1"
assert result[0].resource_id == function_id
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
assert result[0].location == "West Europe"
@@ -0,0 +1,239 @@
from unittest import mock
from uuid import uuid4
from prowler.providers.azure.config import USER_ACCESS_ADMINISTRATOR_ROLE_ID
from tests.providers.azure.azure_fixtures import (
AZURE_SUBSCRIPTION_ID,
set_mocked_azure_provider,
)
class Test_app_function_identity_without_admin_privileges:
def test_app_no_subscriptions(self):
app_client = mock.MagicMock
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_function_identity_without_admin_privileges.app_function_identity_without_admin_privileges.app_client",
new=app_client,
):
from prowler.providers.azure.services.app.app_function_identity_without_admin_privileges.app_function_identity_without_admin_privileges import (
app_function_identity_without_admin_privileges,
)
app_client.functions = {}
check = app_function_identity_without_admin_privileges()
result = check.execute()
assert len(result) == 0
def test_app_subscription_empty(self):
app_client = mock.MagicMock
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_function_identity_without_admin_privileges.app_function_identity_without_admin_privileges.app_client",
new=app_client,
):
from prowler.providers.azure.services.app.app_function_identity_without_admin_privileges.app_function_identity_without_admin_privileges import (
app_function_identity_without_admin_privileges,
)
app_client.functions = {AZURE_SUBSCRIPTION_ID: {}}
check = app_function_identity_without_admin_privileges()
result = check.execute()
assert len(result) == 0
def test_app_function_no_identity(self):
app_client = mock.MagicMock
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_function_identity_without_admin_privileges.app_function_identity_without_admin_privileges.app_client",
new=app_client,
):
from prowler.providers.azure.services.app.app_function_identity_without_admin_privileges.app_function_identity_without_admin_privileges import (
app_function_identity_without_admin_privileges,
)
from prowler.providers.azure.services.app.app_service import FunctionApp
function_id = str(uuid4())
app_client.functions = {
AZURE_SUBSCRIPTION_ID: {
function_id: FunctionApp(
name="function1",
location="West Europe",
kind="functionapp,linux",
function_keys={},
enviroment_variables={},
identity=None,
public_access=False,
vnet_subnet_id=None,
ftps_state="AllAllowed",
)
}
}
check = app_function_identity_without_admin_privileges()
result = check.execute()
assert len(result) == 0
def test_app_function_no_admin_roles(self):
app_client = mock.MagicMock
iam_client = mock.MagicMock
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_function_identity_without_admin_privileges.app_function_identity_without_admin_privileges.app_client",
new=app_client,
), mock.patch(
"prowler.providers.azure.services.app.app_function_identity_without_admin_privileges.app_function_identity_without_admin_privileges.iam_client",
new=iam_client,
):
from prowler.providers.azure.services.app.app_function_identity_without_admin_privileges.app_function_identity_without_admin_privileges import (
app_function_identity_without_admin_privileges,
)
from prowler.providers.azure.services.app.app_service import FunctionApp
from prowler.providers.azure.services.iam.iam_service import (
Role,
RoleAssignment,
)
function_id = str(uuid4())
app_client.functions = {
AZURE_SUBSCRIPTION_ID: {
function_id: FunctionApp(
name="function1",
location="West Europe",
kind="functionapp,linux",
function_keys={},
enviroment_variables={},
identity=mock.MagicMock(principal_id="123"),
public_access=False,
vnet_subnet_id=None,
ftps_state="AllAllowed",
)
}
}
iam_client.role_assignments = {
AZURE_SUBSCRIPTION_ID: {
"1": RoleAssignment(
role_id="1",
agent_id="123",
agent_type="User",
)
}
}
iam_client.roles = {
AZURE_SUBSCRIPTION_ID: [
Role(
id="1",
name="role1",
type="User",
assignable_scopes=[],
permissions=[],
)
]
}
check = app_function_identity_without_admin_privileges()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "Function function1 has a managed identity enabled but without admin privileges."
)
assert result[0].resource_id == function_id
assert result[0].resource_name == "function1"
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
assert result[0].location == "West Europe"
def test_app_function_admin_roles(self):
app_client = mock.MagicMock
iam_client = mock.MagicMock
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_function_identity_without_admin_privileges.app_function_identity_without_admin_privileges.app_client",
new=app_client,
), mock.patch(
"prowler.providers.azure.services.app.app_function_identity_without_admin_privileges.app_function_identity_without_admin_privileges.iam_client",
new=iam_client,
):
from prowler.providers.azure.services.app.app_function_identity_without_admin_privileges.app_function_identity_without_admin_privileges import (
app_function_identity_without_admin_privileges,
)
from prowler.providers.azure.services.app.app_service import FunctionApp
from prowler.providers.azure.services.iam.iam_service import (
Role,
RoleAssignment,
)
function_id = str(uuid4())
app_client.functions = {
AZURE_SUBSCRIPTION_ID: {
function_id: FunctionApp(
name="function1",
location="West Europe",
kind="functionapp,linux",
function_keys={},
enviroment_variables={},
identity=mock.MagicMock(principal_id="123"),
public_access=False,
vnet_subnet_id=None,
ftps_state="AllAllowed",
)
}
}
iam_client.role_assignments = {
AZURE_SUBSCRIPTION_ID: {
"1": RoleAssignment(
role_id=USER_ACCESS_ADMINISTRATOR_ROLE_ID,
agent_id="123",
agent_type="User",
)
}
}
iam_client.roles = {
AZURE_SUBSCRIPTION_ID: [
Role(
id=USER_ACCESS_ADMINISTRATOR_ROLE_ID,
name="User Access Administrator",
type="User",
assignable_scopes=[],
permissions=[],
)
]
}
check = app_function_identity_without_admin_privileges()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "Function function1 has a managed identity enabled and it is configure with admin privileges using role User Access Administrator."
)
assert result[0].resource_id == function_id
assert result[0].resource_name == "function1"
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
assert result[0].location == "West Europe"
@@ -0,0 +1,139 @@
from unittest import mock
from uuid import uuid4
from tests.providers.azure.azure_fixtures import (
AZURE_SUBSCRIPTION_ID,
set_mocked_azure_provider,
)
class Test_app_function_latest_runtime_version:
def test_app_no_subscriptions(self):
app_client = mock.MagicMock
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_function_latest_runtime_version.app_function_latest_runtime_version.app_client",
new=app_client,
):
from prowler.providers.azure.services.app.app_function_latest_runtime_version.app_function_latest_runtime_version import (
app_function_latest_runtime_version,
)
app_client.functions = {}
check = app_function_latest_runtime_version()
result = check.execute()
assert len(result) == 0
def test_app_subscription_empty(self):
app_client = mock.MagicMock
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_function_latest_runtime_version.app_function_latest_runtime_version.app_client",
new=app_client,
):
from prowler.providers.azure.services.app.app_function_latest_runtime_version.app_function_latest_runtime_version import (
app_function_latest_runtime_version,
)
app_client.functions = {AZURE_SUBSCRIPTION_ID: {}}
check = app_function_latest_runtime_version()
result = check.execute()
assert len(result) == 0
def test_app_function_runtime_is_latest(self):
app_client = mock.MagicMock
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_function_latest_runtime_version.app_function_latest_runtime_version.app_client",
new=app_client,
):
from prowler.providers.azure.services.app.app_function_latest_runtime_version.app_function_latest_runtime_version import (
app_function_latest_runtime_version,
)
from prowler.providers.azure.services.app.app_service import FunctionApp
function_id = str(uuid4())
app_client.functions = {
AZURE_SUBSCRIPTION_ID: {
function_id: FunctionApp(
name="function1",
location="West Europe",
kind="functionapp,linux",
function_keys={},
enviroment_variables={"FUNCTIONS_EXTENSION_VERSION": "~4"},
identity=None,
public_access=False,
vnet_subnet_id=None,
ftps_state="AllAllowed",
)
}
}
check = app_function_latest_runtime_version()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert result[0].status_extended == (
"Function function1 is using the latest runtime."
)
assert result[0].resource_id == function_id
assert result[0].resource_name == "function1"
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
assert result[0].location == "West Europe"
def test_app_function_runtime_is_not_latest(self):
app_client = mock.MagicMock
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_function_latest_runtime_version.app_function_latest_runtime_version.app_client",
new=app_client,
):
from prowler.providers.azure.services.app.app_function_latest_runtime_version.app_function_latest_runtime_version import (
app_function_latest_runtime_version,
)
from prowler.providers.azure.services.app.app_service import FunctionApp
function_id = str(uuid4())
app_client.functions = {
AZURE_SUBSCRIPTION_ID: {
function_id: FunctionApp(
name="function1",
location="West Europe",
kind="functionapp,linux",
function_keys={},
enviroment_variables={"FUNCTIONS_EXTENSION_VERSION": "2"},
identity=None,
public_access=False,
vnet_subnet_id=None,
ftps_state="AllAllowed",
)
}
}
check = app_function_latest_runtime_version()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].status_extended == (
"Function function1 is not using the latest runtime. The current runtime is '2' and should be '~4'."
)
assert result[0].resource_id == function_id
assert result[0].resource_name == "function1"
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
assert result[0].location == "West Europe"
@@ -0,0 +1,141 @@
from unittest import mock
from uuid import uuid4
from tests.providers.azure.azure_fixtures import (
AZURE_SUBSCRIPTION_ID,
set_mocked_azure_provider,
)
class Test_app_function_not_publicly_accessible:
def test_app_no_subscriptions(self):
app_client = mock.MagicMock
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_function_not_publicly_accessible.app_function_not_publicly_accessible.app_client",
new=app_client,
):
from prowler.providers.azure.services.app.app_function_not_publicly_accessible.app_function_not_publicly_accessible import (
app_function_not_publicly_accessible,
)
app_client.functions = {}
check = app_function_not_publicly_accessible()
result = check.execute()
assert len(result) == 0
def test_app_subscription_empty(self):
app_client = mock.MagicMock
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_function_not_publicly_accessible.app_function_not_publicly_accessible.app_client",
new=app_client,
):
from prowler.providers.azure.services.app.app_function_not_publicly_accessible.app_function_not_publicly_accessible import (
app_function_not_publicly_accessible,
)
app_client.functions = {AZURE_SUBSCRIPTION_ID: {}}
check = app_function_not_publicly_accessible()
result = check.execute()
assert len(result) == 0
def test_app_function_not_publicly_accessible(self):
app_client = mock.MagicMock
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_function_not_publicly_accessible.app_function_not_publicly_accessible.app_client",
new=app_client,
):
from prowler.providers.azure.services.app.app_function_not_publicly_accessible.app_function_not_publicly_accessible import (
app_function_not_publicly_accessible,
)
from prowler.providers.azure.services.app.app_service import FunctionApp
function_id = str(uuid4())
app_client.functions = {
AZURE_SUBSCRIPTION_ID: {
function_id: FunctionApp(
name="function1",
location="West Europe",
kind="functionapp,linux",
function_keys={},
enviroment_variables={},
identity=mock.MagicMock(type="SystemAssigned"),
public_access=False,
vnet_subnet_id=None,
ftps_state="AllAllowed",
)
}
}
check = app_function_not_publicly_accessible()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "Function function1 is not publicly accessible."
)
assert result[0].resource_name == "function1"
assert result[0].resource_id == function_id
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
assert result[0].location == "West Europe"
def test_app_function_publicly_accessible(self):
app_client = mock.MagicMock
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_function_not_publicly_accessible.app_function_not_publicly_accessible.app_client",
new=app_client,
):
from prowler.providers.azure.services.app.app_function_not_publicly_accessible.app_function_not_publicly_accessible import (
app_function_not_publicly_accessible,
)
from prowler.providers.azure.services.app.app_service import FunctionApp
function_id = str(uuid4())
app_client.functions = {
AZURE_SUBSCRIPTION_ID: {
function_id: FunctionApp(
name="function1",
location="West Europe",
kind="functionapp,linux",
function_keys={},
enviroment_variables={},
identity=mock.MagicMock(type="SystemAssigned"),
public_access=True,
vnet_subnet_id=None,
ftps_state="AllAllowed",
)
}
}
check = app_function_not_publicly_accessible()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "Function function1 is publicly accessible."
)
assert result[0].resource_name == "function1"
assert result[0].resource_id == function_id
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
assert result[0].location == "West Europe"
@@ -0,0 +1,139 @@
from unittest import mock
from uuid import uuid4
from tests.providers.azure.azure_fixtures import (
AZURE_SUBSCRIPTION_ID,
set_mocked_azure_provider,
)
class Test_app_function_vnet_integration_enabled:
def test_app_no_subscriptions(self):
app_client = mock.MagicMock
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_function_vnet_integration_enabled.app_function_vnet_integration_enabled.app_client",
new=app_client,
):
from prowler.providers.azure.services.app.app_function_vnet_integration_enabled.app_function_vnet_integration_enabled import (
app_function_vnet_integration_enabled,
)
app_client.functions = {}
check = app_function_vnet_integration_enabled()
result = check.execute()
assert len(result) == 0
def test_app_subscription_empty(self):
app_client = mock.MagicMock
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_function_vnet_integration_enabled.app_function_vnet_integration_enabled.app_client",
new=app_client,
):
from prowler.providers.azure.services.app.app_function_vnet_integration_enabled.app_function_vnet_integration_enabled import (
app_function_vnet_integration_enabled,
)
app_client.functions = {AZURE_SUBSCRIPTION_ID: {}}
check = app_function_vnet_integration_enabled()
result = check.execute()
assert len(result) == 0
def test_app_function_vnet_integration_enabled(self):
app_client = mock.MagicMock
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_function_vnet_integration_enabled.app_function_vnet_integration_enabled.app_client",
new=app_client,
):
from prowler.providers.azure.services.app.app_function_vnet_integration_enabled.app_function_vnet_integration_enabled import (
app_function_vnet_integration_enabled,
)
from prowler.providers.azure.services.app.app_service import FunctionApp
function_id = str(uuid4())
app_client.functions = {
AZURE_SUBSCRIPTION_ID: {
function_id: FunctionApp(
name="function1",
location="West Europe",
kind="functionapp,linux",
function_keys={},
enviroment_variables={},
identity=None,
public_access=True,
vnet_subnet_id="vnet_subnet_id",
ftps_state="FtpsOnly",
)
}
}
check = app_function_vnet_integration_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "Function function1 has Virtual Network integration enabled with subnet 'vnet_subnet_id' enabled."
)
assert result[0].resource_name == "function1"
assert result[0].resource_id == function_id
assert result[0].location == "West Europe"
def test_app_function_vnet_integration_disabled(self):
app_client = mock.MagicMock
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_function_vnet_integration_enabled.app_function_vnet_integration_enabled.app_client",
new=app_client,
):
from prowler.providers.azure.services.app.app_function_vnet_integration_enabled.app_function_vnet_integration_enabled import (
app_function_vnet_integration_enabled,
)
from prowler.providers.azure.services.app.app_service import FunctionApp
function_id = str(uuid4())
app_client.functions = {
AZURE_SUBSCRIPTION_ID: {
function_id: FunctionApp(
name="function1",
location="West Europe",
kind="functionapp,linux",
function_keys={},
enviroment_variables={},
identity=None,
public_access=True,
vnet_subnet_id=None,
ftps_state="AllAllowed",
)
}
}
check = app_function_vnet_integration_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "Function function1 does not have virtual network integration enabled."
)
assert result[0].resource_name == "function1"
assert result[0].resource_id == function_id
assert result[0].location == "West Europe"
@@ -8,7 +8,7 @@ from tests.providers.azure.azure_fixtures import (
set_mocked_azure_provider,
)
# TODO: we have to fix this test not to use MagicMock but set the App service while mocking the import ot the Monitor client
# TODO: we have to fix this test not to use MagicMock but set the App service while mocking the import of the Monitor client
# def mock_app_get_apps(_):
# return {
# AZURE_SUBSCRIPTION_ID: {
@@ -63,6 +63,7 @@ class Test_appinsights_ensure_is_configured:
resource_id="/subscriptions/resource_id",
resource_name="AppInsightsTest",
location="westeurope",
instrumentation_key="",
)
}
}
@@ -17,6 +17,7 @@ def mock_appinsights_get_components(_):
resource_id="/subscriptions/resource_id",
resource_name="AppInsightsTest",
location="westeurope",
instrumentation_key="",
)
}
}