feat(azure/vm): add new check vm_linux_enforce_ssh_authentication (#8149)

This commit is contained in:
Rubén De la Torre Vico
2025-07-07 16:01:11 +02:00
committed by GitHub
parent 4477cecc59
commit ab96e0aac0
8 changed files with 288 additions and 0 deletions
+2
View File
@@ -7,6 +7,8 @@ All notable changes to the **Prowler SDK** are documented in this file.
### Added
- `storage_smb_channel_encryption_with_secure_algorithm` check for Azure provider [(#8123)](https://github.com/prowler-cloud/prowler/pull/8123)
- `vm_linux_enforce_ssh_authentication` check for Azure provider [(#8149)](https://github.com/prowler-cloud/prowler/pull/8149)
### Changed
### Fixed
@@ -0,0 +1,30 @@
{
"Provider": "azure",
"CheckID": "vm_linux_enforce_ssh_authentication",
"CheckTitle": "Ensure SSH key authentication is enforced on Linux-based Virtual Machines",
"CheckType": [],
"ServiceName": "vm",
"SubServiceName": "",
"ResourceIdTemplate": "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Compute/virtualMachines/{vmName}",
"Severity": "high",
"ResourceType": "Microsoft.Compute/virtualMachines",
"Description": "Ensure that Azure Linux-based virtual machines are configured to use SSH keys by disabling password authentication.",
"Risk": "Allowing password-based SSH authentication increases the risk of brute-force attacks and unauthorized access. Enforcing SSH key authentication ensures only users with the private key can access the VM.",
"RelatedUrl": "https://docs.microsoft.com/en-us/azure/virtual-machines/linux/create-ssh-keys-detailed",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/azure/VirtualMachines/ssh-authentication-type.html",
"Terraform": ""
},
"Recommendation": {
"Text": "Recreate Linux VMs with SSH key authentication enabled and password authentication disabled.",
"Url": "https://docs.microsoft.com/en-us/azure/virtual-machines/linux/create-ssh-keys-detailed"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,29 @@
from prowler.lib.check.models import Check, Check_Report_Azure
from prowler.providers.azure.services.vm.vm_client import vm_client
class vm_linux_enforce_ssh_authentication(Check):
"""
Ensure that Azure Linux-based virtual machines are configured to use SSH keys (password authentication is disabled).
This check evaluates whether disablePasswordAuthentication is set to True for Linux VMs to ensure only SSH key authentication is allowed.
- PASS: VM has password authentication disabled (SSH key authentication enforced).
- FAIL: VM has password authentication enabled (password-based SSH allowed).
"""
def execute(self) -> list[Check_Report_Azure]:
findings = []
for subscription_name, vms in vm_client.virtual_machines.items():
for vm in vms.values():
if vm.linux_configuration:
report = Check_Report_Azure(metadata=self.metadata(), resource=vm)
report.subscription = subscription_name
if vm.linux_configuration.disable_password_authentication:
report.status = "PASS"
report.status_extended = f"VM {vm.resource_name} in subscription {subscription_name} has password authentication disabled (SSH key authentication enforced)."
else:
report.status = "FAIL"
report.status_extended = f"VM {vm.resource_name} in subscription {subscription_name} has password authentication enabled (password-based SSH allowed)."
findings.append(report)
return findings
@@ -62,6 +62,18 @@ class VirtualMachines(AzureService):
if extension
]
# Collect LinuxConfiguration.disablePasswordAuthentication if available
linux_configuration = None
os_profile = getattr(vm, "os_profile", None)
if os_profile:
linux_conf = getattr(os_profile, "linux_configuration", None)
if linux_conf:
linux_configuration = LinuxConfiguration(
disable_password_authentication=getattr(
linux_conf, "disable_password_authentication", False
)
)
virtual_machines[subscription_name].update(
{
vm.id: VirtualMachine(
@@ -92,6 +104,7 @@ class VirtualMachines(AzureService):
location=vm.location,
security_profile=getattr(vm, "security_profile", None),
extensions=extensions,
linux_configuration=linux_configuration,
)
}
)
@@ -180,6 +193,10 @@ class VirtualMachineExtension(BaseModel):
id: str
class LinuxConfiguration(BaseModel):
disable_password_authentication: bool
class VirtualMachine(BaseModel):
resource_id: str
resource_name: str
@@ -187,6 +204,7 @@ class VirtualMachine(BaseModel):
security_profile: Optional[SecurityProfile]
extensions: list[VirtualMachineExtension]
storage_profile: Optional[StorageProfile] = None
linux_configuration: Optional[LinuxConfiguration] = None
class Disk(BaseModel):
@@ -86,6 +86,7 @@ class Test_vm_ensure_using_managed_disks:
),
data_disks=[],
),
linux_configuration=None,
),
}
}
@@ -142,6 +143,7 @@ class Test_vm_ensure_using_managed_disks:
),
data_disks=[],
),
linux_configuration=None,
)
}
}
@@ -200,6 +202,7 @@ class Test_vm_ensure_using_managed_disks:
DataDisk(lun=0, name="data_disk_1", managed_disk=None)
],
),
linux_configuration=None,
)
}
}
@@ -0,0 +1,173 @@
from unittest import mock
from uuid import uuid4
from prowler.providers.azure.services.vm.vm_service import (
LinuxConfiguration,
VirtualMachine,
)
from tests.providers.azure.azure_fixtures import (
AZURE_SUBSCRIPTION_ID,
set_mocked_azure_provider,
)
class Test_vm_linux_enforce_ssh_authentication:
def test_no_subscriptions(self):
vm_client = mock.MagicMock
vm_client.virtual_machines = {}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_azure_provider(),
),
mock.patch(
"prowler.providers.azure.services.vm.vm_linux_enforce_ssh_authentication.vm_linux_enforce_ssh_authentication.vm_client",
new=vm_client,
),
):
from prowler.providers.azure.services.vm.vm_linux_enforce_ssh_authentication.vm_linux_enforce_ssh_authentication import (
vm_linux_enforce_ssh_authentication,
)
check = vm_linux_enforce_ssh_authentication()
result = check.execute()
assert len(result) == 0
def test_empty_subscription(self):
vm_client = mock.MagicMock
vm_client.virtual_machines = {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.vm.vm_linux_enforce_ssh_authentication.vm_linux_enforce_ssh_authentication.vm_client",
new=vm_client,
),
):
from prowler.providers.azure.services.vm.vm_linux_enforce_ssh_authentication.vm_linux_enforce_ssh_authentication import (
vm_linux_enforce_ssh_authentication,
)
check = vm_linux_enforce_ssh_authentication()
result = check.execute()
assert len(result) == 0
def test_linux_vm_password_auth_disabled(self):
vm_id = str(uuid4())
vm_client = mock.MagicMock
vm_client.virtual_machines = {
AZURE_SUBSCRIPTION_ID: {
vm_id: VirtualMachine(
resource_id=vm_id,
resource_name="LinuxVM",
location="westeurope",
security_profile=None,
extensions=[],
storage_profile=None,
linux_configuration=LinuxConfiguration(
disable_password_authentication=True
),
)
}
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_azure_provider(),
),
mock.patch(
"prowler.providers.azure.services.vm.vm_linux_enforce_ssh_authentication.vm_linux_enforce_ssh_authentication.vm_client",
new=vm_client,
),
):
from prowler.providers.azure.services.vm.vm_linux_enforce_ssh_authentication.vm_linux_enforce_ssh_authentication import (
vm_linux_enforce_ssh_authentication,
)
check = vm_linux_enforce_ssh_authentication()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
assert result[0].resource_name == "LinuxVM"
assert result[0].resource_id == vm_id
assert "password authentication disabled" in result[0].status_extended
def test_linux_vm_password_auth_enabled(self):
vm_id = str(uuid4())
vm_client = mock.MagicMock
vm_client.virtual_machines = {
AZURE_SUBSCRIPTION_ID: {
vm_id: VirtualMachine(
resource_id=vm_id,
resource_name="LinuxVM",
location="westeurope",
security_profile=None,
extensions=[],
storage_profile=None,
linux_configuration=LinuxConfiguration(
disable_password_authentication=False
),
)
}
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_azure_provider(),
),
mock.patch(
"prowler.providers.azure.services.vm.vm_linux_enforce_ssh_authentication.vm_linux_enforce_ssh_authentication.vm_client",
new=vm_client,
),
):
from prowler.providers.azure.services.vm.vm_linux_enforce_ssh_authentication.vm_linux_enforce_ssh_authentication import (
vm_linux_enforce_ssh_authentication,
)
check = vm_linux_enforce_ssh_authentication()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
assert result[0].resource_name == "LinuxVM"
assert result[0].resource_id == vm_id
assert "password authentication enabled" in result[0].status_extended
def test_non_linux_vm(self):
vm_id = str(uuid4())
vm_client = mock.MagicMock
vm_client.virtual_machines = {
AZURE_SUBSCRIPTION_ID: {
vm_id: VirtualMachine(
resource_id=vm_id,
resource_name="WindowsVM",
location="westeurope",
security_profile=None,
extensions=[],
storage_profile=None,
linux_configuration=None, # Not a Linux VM
)
}
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_azure_provider(),
),
mock.patch(
"prowler.providers.azure.services.vm.vm_linux_enforce_ssh_authentication.vm_linux_enforce_ssh_authentication.vm_client",
new=vm_client,
),
):
from prowler.providers.azure.services.vm.vm_linux_enforce_ssh_authentication.vm_linux_enforce_ssh_authentication import (
vm_linux_enforce_ssh_authentication,
)
check = vm_linux_enforce_ssh_authentication()
result = check.execute()
assert len(result) == 0
@@ -2,6 +2,7 @@ from unittest.mock import patch
from prowler.providers.azure.services.vm.vm_service import (
Disk,
LinuxConfiguration,
ManagedDiskParameters,
OperatingSystemType,
OSDisk,
@@ -40,6 +41,7 @@ def mock_vm_get_virtual_machines(_):
),
data_disks=[],
),
linux_configuration=None,
)
}
}
@@ -55,6 +57,7 @@ def mock_vm_get_virtual_machines_with_none(_):
security_profile=None,
extensions=[],
storage_profile=None,
linux_configuration=None,
),
"vm_id-2": VirtualMachine(
resource_id="/subscriptions/resource_id2",
@@ -66,6 +69,7 @@ def mock_vm_get_virtual_machines_with_none(_):
os_disk=None,
data_disks=[],
),
linux_configuration=None,
),
}
}
@@ -85,6 +89,24 @@ def mock_vm_get_disks(_):
}
def mock_vm_get_virtual_machines_with_linux(_):
return {
AZURE_SUBSCRIPTION_ID: {
"vm_id-linux": VirtualMachine(
resource_id="/subscriptions/resource_id_linux",
resource_name="LinuxVM",
location="location",
security_profile=None,
extensions=[],
storage_profile=None,
linux_configuration=LinuxConfiguration(
disable_password_authentication=True
),
)
}
}
@patch(
"prowler.providers.azure.services.vm.vm_service.VirtualMachines._get_virtual_machines",
new=mock_vm_get_virtual_machines,
@@ -186,3 +208,14 @@ class Test_VirtualMachines_NoneCases:
assert vm_2.storage_profile.os_disk is None
assert vm_2.storage_profile.data_disks == []
assert vm_2.resource_name == "VMWithPartialNone"
@patch(
"prowler.providers.azure.services.vm.vm_service.VirtualMachines._get_virtual_machines",
new=mock_vm_get_virtual_machines_with_linux,
)
def test_virtual_machine_with_linux_configuration():
virtual_machines = VirtualMachines(set_mocked_azure_provider())
vm = virtual_machines.virtual_machines[AZURE_SUBSCRIPTION_ID]["vm_id-linux"]
assert vm.linux_configuration is not None
assert vm.linux_configuration.disable_password_authentication is True