Skip to content

Commit

Permalink
{AKS} Fix compatibility issue when enabling Microsoft Defender via ak…
Browse files Browse the repository at this point in the history
…s-preview (#5106)
  • Loading branch information
FumingZhang authored Jul 13, 2022
1 parent e9801ef commit fc3a348
Show file tree
Hide file tree
Showing 6 changed files with 327 additions and 3 deletions.
11 changes: 9 additions & 2 deletions src/aks-preview/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,23 @@ To release a new version, please select a new version number (usually plus 1 to

Pending
+++++++

0.5.90
++++++

* Fix compatibility issue when enabling Microsoft Defender via aks-preview.
* az aks create
* az aks update

0.5.89
++++++

* Fix for the az aks addon list command to return enable:true, if virtual-node addon is enabled for the AKS cluster.

+++++++
0.5.88
++++++

* AKS Monitoring MSI Auth related code imported from Azure CLI to reuse the code between aks-preview and Azure CLI
* AKS Monitoring MSI Auth related code imported from Azure CLI to reuse the code between aks-preview and Azure CLI.

0.5.87
++++++
Expand Down
84 changes: 84 additions & 0 deletions src/aks-preview/azext_aks_preview/managed_cluster_decorator.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
ManagedClusterStorageProfileBlobCSIDriver = TypeVar('ManagedClusterStorageProfileBlobCSIDriver')
ManagedClusterStorageProfileSnapshotController = TypeVar('ManagedClusterStorageProfileSnapshotController')
ManagedClusterIngressProfileWebAppRouting = TypeVar("ManagedClusterIngressProfileWebAppRouting")
ManagedClusterSecurityProfileDefender = TypeVar("ManagedClusterSecurityProfileDefender")


# pylint: disable=too-few-public-methods
Expand Down Expand Up @@ -1333,6 +1334,53 @@ def get_disable_keda(self) -> bool:
"""
return self._get_disable_keda(enable_validation=True)

def get_defender_config(self) -> Union[ManagedClusterSecurityProfileDefender, None]:
"""Obtain the value of defender.
Note: Overwritten in aks-preview to adapt to v2 defender structure.
:return: ManagedClusterSecurityProfileDefender or None
"""
disable_defender = self.raw_param.get("disable_defender")
if disable_defender:
return self.models.ManagedClusterSecurityProfileDefender(
security_monitoring=self.models.ManagedClusterSecurityProfileDefenderSecurityMonitoring(
enabled=False
)
)

enable_defender = self.raw_param.get("enable_defender")

if not enable_defender:
return None

workspace = ""
config_file_path = self.raw_param.get("defender_config")
if config_file_path:
if not os.path.isfile(config_file_path):
raise InvalidArgumentValueError(
"{} is not valid file, or not accessable.".format(
config_file_path
)
)
defender_config = get_file_json(config_file_path)
if "logAnalyticsWorkspaceResourceId" in defender_config:
workspace = defender_config["logAnalyticsWorkspaceResourceId"]

if workspace == "":
workspace = self.external_functions.ensure_default_log_analytics_workspace_for_monitoring(
self.cmd,
self.get_subscription_id(),
self.get_resource_group_name())

azure_defender = self.models.ManagedClusterSecurityProfileDefender(
log_analytics_workspace_resource_id=workspace,
security_monitoring=self.models.ManagedClusterSecurityProfileDefenderSecurityMonitoring(
enabled=enable_defender
),
)
return azure_defender


class AKSPreviewManagedClusterCreateDecorator(AKSManagedClusterCreateDecorator):
def __init__(
Expand Down Expand Up @@ -1621,6 +1669,24 @@ def set_up_workload_auto_scaler_profile(self, mc: ManagedCluster) -> ManagedClus

return mc

def set_up_defender(self, mc: ManagedCluster) -> ManagedCluster:
"""Set up defender for the ManagedCluster object.
Note: Overwritten in aks-preview to adapt to v2 defender structure.
:return: the ManagedCluster object
"""
self._ensure_mc(mc)

defender = self.context.get_defender_config()
if defender:
if mc.security_profile is None:
mc.security_profile = self.models.ManagedClusterSecurityProfile()

mc.security_profile.defender = defender

return mc

def construct_mc_profile_preview(self, bypass_restore_defaults: bool = False) -> ManagedCluster:
"""The overall controller used to construct the default ManagedCluster profile.
Expand Down Expand Up @@ -1930,6 +1996,24 @@ def update_workload_auto_scaler_profile(self, mc: ManagedCluster) -> ManagedClus

return mc

def update_defender(self, mc: ManagedCluster) -> ManagedCluster:
"""Update defender for the ManagedCluster object.
Note: Overwritten in aks-preview to adapt to v2 defender structure.
:return: the ManagedCluster object
"""
self._ensure_mc(mc)

defender = self.context.get_defender_config()
if defender:
if mc.security_profile is None:
mc.security_profile = self.models.ManagedClusterSecurityProfile()

mc.security_profile.defender = defender

return mc

def update_mc_profile_preview(self) -> ManagedCluster:
"""The overall controller used to update the preview ManagedCluster profile.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"logAnalyticsWorkspaceResourceId": "test_workspace_resource_id"
}
Original file line number Diff line number Diff line change
Expand Up @@ -4923,3 +4923,60 @@ def test_aks_availability_zones(self, resource_group, resource_group_location):
# delete
self.cmd(
'aks delete -g {resource_group} -n {name} --yes --no-wait', checks=[self.is_empty()])

# live only due to workspace is not mocked correctly
@live_only()
@AllowLargeResponse()
@AKSCustomResourceGroupPreparer(random_name_length=17, name_prefix='clitest', location='westus2')
def test_aks_create_with_defender(self, resource_group, resource_group_location):
aks_name = self.create_random_name('cliakstest', 16)
self.kwargs.update({
'name': aks_name,
'resource_group': resource_group,
'ssh_key_value': self.generate_ssh_keys()
})

# create
create_cmd = 'aks create --resource-group={resource_group} --name={name} ' \
'--ssh-key-value={ssh_key_value} --enable-defender'
self.cmd(create_cmd, checks=[
self.check('provisioningState', 'Succeeded'),
self.check('securityProfile.defender.securityMonitoring.enabled', True)
])

# delete
self.cmd(
'aks delete -g {resource_group} -n {name} --yes --no-wait', checks=[self.is_empty()])

# live only due to workspace is not mocked correctly
@live_only()
@AllowLargeResponse()
@AKSCustomResourceGroupPreparer(random_name_length=17, name_prefix='clitest', location='westus2')
def test_aks_update_with_defender(self, resource_group, resource_group_location):
aks_name = self.create_random_name('cliakstest', 16)
self.kwargs.update({
'resource_group': resource_group,
'name': aks_name,
'ssh_key_value': self.generate_ssh_keys()
})

create_cmd = 'aks create --resource-group={resource_group} --name={name} --ssh-key-value={ssh_key_value}'
self.cmd(create_cmd, checks=[
self.check('provisioningState', 'Succeeded'),
])

# update to enable defender
self.cmd('aks update --resource-group={resource_group} --name={name} --enable-defender', checks=[
self.check('provisioningState', 'Succeeded'),
self.check('securityProfile.defender.securityMonitoring.enabled', True)
])

# update to disable defender
self.cmd('aks update --resource-group={resource_group} --name={name} --disable-defender', checks=[
self.check('provisioningState', 'Succeeded'),
self.check('securityProfile.defender.securityMonitoring.enabled', False)
])

# delete
self.cmd(
'aks delete -g {resource_group} -n {name} --yes --no-wait', checks=[self.is_empty()])
Original file line number Diff line number Diff line change
Expand Up @@ -2248,6 +2248,78 @@ def test_get_disable_keda(self):
with self.assertRaises(MutuallyExclusiveArgumentError):
ctx_5.get_disable_keda()

def test_get_defender_config(self):
ctx_1 = AKSPreviewManagedClusterContext(
self.cmd,
AKSManagedClusterParamDict(
{
"enable_defender": True,
"defender_config": get_test_data_file_path(
"defenderconfig.json"
),
}
),
self.models,
DecoratorMode.CREATE,
)
defender_config_1 = ctx_1.get_defender_config()
ground_truth_defender_config_1 = self.models.ManagedClusterSecurityProfileDefender(
log_analytics_workspace_resource_id="test_workspace_resource_id",
security_monitoring=self.models.ManagedClusterSecurityProfileDefenderSecurityMonitoring(
enabled=True
),
)
self.assertEqual(defender_config_1, ground_truth_defender_config_1)

# custom value
ctx_2 = AKSPreviewManagedClusterContext(
self.cmd,
AKSManagedClusterParamDict(
{"enable_defender": True, "defender_config": "fake-path"}
),
self.models,
DecoratorMode.CREATE,
)
# fail on invalid file path
with self.assertRaises(InvalidArgumentValueError):
ctx_2.get_defender_config()

# custom
ctx_3 = AKSPreviewManagedClusterContext(
self.cmd,
AKSManagedClusterParamDict({"disable_defender": True}),
self.models,
DecoratorMode.UPDATE,
)
defender_config_3 = ctx_3.get_defender_config()
ground_truth_defender_config_3 = self.models.ManagedClusterSecurityProfileDefender(
security_monitoring=self.models.ManagedClusterSecurityProfileDefenderSecurityMonitoring(
enabled=False,
),
)
self.assertEqual(defender_config_3, ground_truth_defender_config_3)

# custom
ctx_4 = AKSPreviewManagedClusterContext(
self.cmd,
AKSManagedClusterParamDict({"enable_defender": True}),
self.models,
DecoratorMode.UPDATE,
)
ctx_4.set_intermediate("subscription_id", "test_subscription_id")
with patch(
"azure.cli.command_modules.acs.managed_cluster_decorator.ensure_default_log_analytics_workspace_for_monitoring",
return_value="test_workspace_resource_id",
):
defender_config_4 = ctx_4.get_defender_config()
ground_truth_defender_config_4 = self.models.ManagedClusterSecurityProfileDefender(
log_analytics_workspace_resource_id="test_workspace_resource_id",
security_monitoring=self.models.ManagedClusterSecurityProfileDefenderSecurityMonitoring(
enabled=True,
),
)
self.assertEqual(defender_config_4, ground_truth_defender_config_4)


class AKSPreviewManagedClusterCreateDecoratorTestCase(unittest.TestCase):
def setUp(self):
Expand Down Expand Up @@ -2913,6 +2985,36 @@ def test_set_up_workload_auto_scaler_profile(self):
self.assertIsNotNone(mc_out.workload_auto_scaler_profile.keda)
self.assertTrue(mc_out.workload_auto_scaler_profile.keda.enabled)

def test_set_up_defender(self):
dec_1 = AKSPreviewManagedClusterCreateDecorator(
self.cmd,
self.client,
{"enable_defender": True},
CUSTOM_MGMT_AKS_PREVIEW,
)
mc_1 = self.models.ManagedCluster(location="test_location")
dec_1.context.attach_mc(mc_1)
dec_1.context.set_intermediate("subscription_id", "test_subscription_id")

with patch(
"azure.cli.command_modules.acs.managed_cluster_decorator.ensure_default_log_analytics_workspace_for_monitoring",
return_value="test_workspace_resource_id",
):
dec_mc_1 = dec_1.set_up_defender(mc_1)

ground_truth_mc_1 = self.models.ManagedCluster(
location="test_location",
security_profile=self.models.ManagedClusterSecurityProfile(
defender=self.models.ManagedClusterSecurityProfileDefender(
log_analytics_workspace_resource_id="test_workspace_resource_id",
security_monitoring=self.models.ManagedClusterSecurityProfileDefenderSecurityMonitoring(
enabled=True
),
)
),
)
self.assertEqual(dec_mc_1, ground_truth_mc_1)

def test_construct_mc_profile_preview(self):
import inspect

Expand Down Expand Up @@ -3990,6 +4092,77 @@ def test_update_workload_auto_scaler_profile(self):
with self.assertRaises(MutuallyExclusiveArgumentError):
mc_out = dec_9.update_workload_auto_scaler_profile(mc_in)

def test_update_defender(self):
# enable
dec_1 = AKSPreviewManagedClusterUpdateDecorator(
self.cmd,
self.client,
{
"enable_defender": True,
"defender_config": get_test_data_file_path(
"defenderconfig.json"
),
},
CUSTOM_MGMT_AKS_PREVIEW,
)
mc_1 = self.models.ManagedCluster(location="test_location")
dec_1.context.attach_mc(mc_1)
dec_1.context.set_intermediate(
"subscription_id", "test_subscription_id"
)

dec_mc_1 = dec_1.update_defender(mc_1)

ground_truth_mc_1 = self.models.ManagedCluster(
location="test_location",
security_profile=self.models.ManagedClusterSecurityProfile(
defender=self.models.ManagedClusterSecurityProfileDefender(
log_analytics_workspace_resource_id="test_workspace_resource_id",
security_monitoring=self.models.ManagedClusterSecurityProfileDefenderSecurityMonitoring(
enabled=True
),
)
),
)
self.assertEqual(dec_mc_1, ground_truth_mc_1)

# disable
dec_2 = AKSPreviewManagedClusterUpdateDecorator(
self.cmd,
self.client,
{"disable_defender": True},
CUSTOM_MGMT_AKS_PREVIEW,
)
mc_2 = self.models.ManagedCluster(
location="test_location",
security_profile=self.models.ManagedClusterSecurityProfile(
defender=self.models.ManagedClusterSecurityProfileDefender(
log_analytics_workspace_resource_id="test_workspace_resource_id",
security_monitoring=self.models.ManagedClusterSecurityProfileDefenderSecurityMonitoring(
enabled=True
),
)
),
)
dec_2.context.attach_mc(mc_2)
dec_2.context.set_intermediate(
"subscription_id", "test_subscription_id"
)

dec_mc_2 = dec_2.update_defender(mc_2)

ground_truth_mc_2 = self.models.ManagedCluster(
location="test_location",
security_profile=self.models.ManagedClusterSecurityProfile(
defender=self.models.ManagedClusterSecurityProfileDefender(
security_monitoring=self.models.ManagedClusterSecurityProfileDefenderSecurityMonitoring(
enabled=False
),
)
),
)
self.assertEqual(dec_mc_2, ground_truth_mc_2)

def test_update_mc_profile_preview(self):
import inspect

Expand Down
Loading

0 comments on commit fc3a348

Please sign in to comment.