mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-24 13:01:56 +00:00
feat(azure/vm): add new check vm_linux_enforce_ssh_authentication (#8149)
This commit is contained in:
committed by
GitHub
parent
4477cecc59
commit
ab96e0aac0
@@ -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
|
||||
|
||||
+30
@@ -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": ""
|
||||
}
|
||||
+29
@@ -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):
|
||||
|
||||
+3
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+173
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user