Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

{AKS} Fix compatibility issue when enabling Microsoft Defender via aks-preview #5106

Merged
merged 6 commits into from
Jul 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -4917,3 +4917,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