diff --git a/src/azure-cli/azure/cli/command_modules/network/aaz/latest/network/watcher/flow_log/_create.py b/src/azure-cli/azure/cli/command_modules/network/aaz/latest/network/watcher/flow_log/_create.py index 447b2d5f291..f094bdad574 100644 --- a/src/azure-cli/azure/cli/command_modules/network/aaz/latest/network/watcher/flow_log/_create.py +++ b/src/azure-cli/azure/cli/command_modules/network/aaz/latest/network/watcher/flow_log/_create.py @@ -43,12 +43,15 @@ class Create(AAZCommand): :example: Create or update flow log az network watcher flow-log create --location westus --resource-group MtRGContainingVNet --name MyVNetName-flowlog --vnet MyVNetName --storage-account MyStorageAccountName --filtering-criteria "dstip=20.252.145.59 || DstPort=443" + + :example: Create flow log with recordtypes filtering + az network watcher flow-log create --resource-group rg1 --network-watcher-name nw1 --name fl --location centraluseuap --target-resource-id /subscriptions/subid/resourceGroups/rg1/providers/Microsoft.Network/networkSecurityGroups/desmondcentral-nsg --storage-account /subscriptions/subid/resourceGroups/rg1/providers/Microsoft.Storage/storageAccounts/nwtest1mgvbfmqsigdxe --filtering-criteria srcIP=158.255.7.8 || dstPort=56891 --record-types B,E --enabled True --format JSON --log-version 1 --identity "{type:UserAssigned,user-assigned-identities:{/subscriptions/subid/resourceGroups/rg1/providers/Microsoft.ManagedIdentity/userAssignedIdentities/id1:{}}}" """ _aaz_info = { - "version": "2024-03-01", + "version": "2025-03-01", "resources": [ - ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.network/networkwatchers/{}/flowlogs/{}", "2024-03-01"], + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.network/networkwatchers/{}/flowlogs/{}", "2025-03-01"], ] } @@ -130,17 +133,29 @@ def _build_arguments_schema(cls, *args, **kwargs): ) identity = cls._args_schema.identity - + identity.mi_system_assigned = AAZStrArg( + options=["system-assigned", "mi-system-assigned"], + help="Set the system managed identity.", + blank="True", + ) identity.type = AAZStrArg( options=["type"], help="The type of identity used for the resource. The type 'SystemAssigned, UserAssigned' includes both an implicitly created identity and a set of user assigned identities. The type 'None' will remove any identities from the virtual machine.", enum={"None": "None", "SystemAssigned": "SystemAssigned", "SystemAssigned, UserAssigned": "SystemAssigned, UserAssigned", "UserAssigned": "UserAssigned"}, ) + identity.mi_user_assigned = AAZListArg( + options=["user-assigned", "mi-user-assigned"], + help="Set the user managed identities.", + blank=[], + ) identity.user_assigned_identities = AAZDictArg( options=["user-assigned-identities"], help="The list of user identities associated with resource. The user identity dictionary key references will be ARM resource ids in the form: '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ManagedIdentity/userAssignedIdentities/{identityName}'.", ) + mi_user_assigned = cls._args_schema.identity.mi_user_assigned + mi_user_assigned.Element = AAZStrArg() + user_assigned_identities = cls._args_schema.identity.user_assigned_identities user_assigned_identities.Element = AAZObjectArg( blank={}, @@ -159,6 +174,11 @@ def _build_arguments_schema(cls, *args, **kwargs): arg_group="Properties", help="Parameters that define the configuration of traffic analytics.", ) + _args_schema.record_types = AAZStrArg( + options=["--record-types"], + arg_group="Properties", + help="Optional field to filter network traffic logs based on flow states. Value of this field could be any comma separated combination string of letters B,C,E or D. B represents Begin, when a flow is created. C represents Continue for an ongoing flow generated at every five-minute interval. E represents End, when a flow is terminated. D represents Deny, when a flow is denied. If not specified, all network traffic will be logged.", + ) _args_schema.retention_policy = AAZObjectArg( options=["--retention-policy"], arg_group="Properties", @@ -290,7 +310,7 @@ def url_parameters(self): def query_parameters(self): parameters = { **self.serialize_query_param( - "api-version", "2024-03-01", + "api-version", "2025-03-01", required=True, ), } @@ -315,7 +335,7 @@ def content(self): typ=AAZObjectType, typ_kwargs={"flags": {"required": True, "client_flatten": True}} ) - _builder.set_prop("identity", AAZObjectType, ".identity") + _builder.set_prop("identity", AAZIdentityObjectType, ".identity") _builder.set_prop("location", AAZStrType, ".location") _builder.set_prop("properties", AAZObjectType, typ_kwargs={"flags": {"client_flatten": True}}) _builder.set_prop("tags", AAZDictType, ".tags") @@ -324,11 +344,16 @@ def content(self): if identity is not None: identity.set_prop("type", AAZStrType, ".type") identity.set_prop("userAssignedIdentities", AAZDictType, ".user_assigned_identities") + identity.set_prop("userAssigned", AAZListType, ".mi_user_assigned", typ_kwargs={"flags": {"action": "create"}}) + identity.set_prop("systemAssigned", AAZStrType, ".mi_system_assigned", typ_kwargs={"flags": {"action": "create"}}) user_assigned_identities = _builder.get(".identity.userAssignedIdentities") if user_assigned_identities is not None: user_assigned_identities.set_elements(AAZObjectType, ".") + user_assigned = _builder.get(".identity.userAssigned") + if user_assigned is not None: + user_assigned.set_elements(AAZStrType, ".") properties = _builder.get(".properties") if properties is not None: @@ -336,6 +361,7 @@ def content(self): properties.set_prop("enabledFilteringCriteria", AAZStrType, ".filtering_criteria") properties.set_prop("flowAnalyticsConfiguration", AAZObjectType, ".flow_analytics_configuration") properties.set_prop("format", AAZObjectType) + properties.set_prop("recordTypes", AAZStrType, ".record_types") properties.set_prop("retentionPolicy", AAZObjectType, ".retention_policy") properties.set_prop("storageId", AAZStrType, ".storage_account", typ_kwargs={"flags": {"required": True}}) properties.set_prop("targetResourceId", AAZStrType, ".target_resource_id", typ_kwargs={"flags": {"required": True}}) @@ -390,7 +416,7 @@ def _build_schema_on_200_201(cls): flags={"read_only": True}, ) _schema_on_200_201.id = AAZStrType() - _schema_on_200_201.identity = AAZObjectType() + _schema_on_200_201.identity = AAZIdentityObjectType() _schema_on_200_201.location = AAZStrType() _schema_on_200_201.name = AAZStrType( flags={"read_only": True}, @@ -443,6 +469,9 @@ def _build_schema_on_200_201(cls): serialized_name="provisioningState", flags={"read_only": True}, ) + properties.record_types = AAZStrType( + serialized_name="recordTypes", + ) properties.retention_policy = AAZObjectType( serialized_name="retentionPolicy", ) diff --git a/src/azure-cli/azure/cli/command_modules/network/aaz/latest/network/watcher/flow_log/_delete.py b/src/azure-cli/azure/cli/command_modules/network/aaz/latest/network/watcher/flow_log/_delete.py index 925d80abc62..d0252af9918 100644 --- a/src/azure-cli/azure/cli/command_modules/network/aaz/latest/network/watcher/flow_log/_delete.py +++ b/src/azure-cli/azure/cli/command_modules/network/aaz/latest/network/watcher/flow_log/_delete.py @@ -22,9 +22,9 @@ class Delete(AAZCommand): """ _aaz_info = { - "version": "2024-03-01", + "version": "2025-03-01", "resources": [ - ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.network/networkwatchers/{}/flowlogs/{}", "2024-03-01"], + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.network/networkwatchers/{}/flowlogs/{}", "2025-03-01"], ] } @@ -152,7 +152,7 @@ def url_parameters(self): def query_parameters(self): parameters = { **self.serialize_query_param( - "api-version", "2024-03-01", + "api-version", "2025-03-01", required=True, ), } diff --git a/src/azure-cli/azure/cli/command_modules/network/aaz/latest/network/watcher/flow_log/_show.py b/src/azure-cli/azure/cli/command_modules/network/aaz/latest/network/watcher/flow_log/_show.py index 1de612847db..a9ad98bea25 100644 --- a/src/azure-cli/azure/cli/command_modules/network/aaz/latest/network/watcher/flow_log/_show.py +++ b/src/azure-cli/azure/cli/command_modules/network/aaz/latest/network/watcher/flow_log/_show.py @@ -25,9 +25,9 @@ class Show(AAZCommand): """ _aaz_info = { - "version": "2024-03-01", + "version": "2025-03-01", "resources": [ - ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.network/networkwatchers/{}/flowlogs/{}", "2024-03-01"], + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.network/networkwatchers/{}/flowlogs/{}", "2025-03-01"], ] } @@ -133,7 +133,7 @@ def url_parameters(self): def query_parameters(self): parameters = { **self.serialize_query_param( - "api-version", "2024-03-01", + "api-version", "2025-03-01", required=True, ), } @@ -170,6 +170,7 @@ def _build_schema_on_200(cls): flags={"read_only": True}, ) _schema_on_200.id = AAZStrType() + _schema_on_200.identity = AAZIdentityObjectType() _schema_on_200.location = AAZStrType() _schema_on_200.name = AAZStrType( flags={"read_only": True}, @@ -182,6 +183,33 @@ def _build_schema_on_200(cls): flags={"read_only": True}, ) + identity = cls._schema_on_200.identity + identity.principal_id = AAZStrType( + serialized_name="principalId", + flags={"read_only": True}, + ) + identity.tenant_id = AAZStrType( + serialized_name="tenantId", + flags={"read_only": True}, + ) + identity.type = AAZStrType() + identity.user_assigned_identities = AAZDictType( + serialized_name="userAssignedIdentities", + ) + + user_assigned_identities = cls._schema_on_200.identity.user_assigned_identities + user_assigned_identities.Element = AAZObjectType() + + _element = cls._schema_on_200.identity.user_assigned_identities.Element + _element.client_id = AAZStrType( + serialized_name="clientId", + flags={"read_only": True}, + ) + _element.principal_id = AAZStrType( + serialized_name="principalId", + flags={"read_only": True}, + ) + properties = cls._schema_on_200.properties properties.enabled = AAZBoolType() properties.enabled_filtering_criteria = AAZStrType( @@ -195,6 +223,9 @@ def _build_schema_on_200(cls): serialized_name="provisioningState", flags={"read_only": True}, ) + properties.record_types = AAZStrType( + serialized_name="recordTypes", + ) properties.retention_policy = AAZObjectType( serialized_name="retentionPolicy", ) diff --git a/src/azure-cli/azure/cli/command_modules/network/aaz/latest/network/watcher/flow_log/_update.py b/src/azure-cli/azure/cli/command_modules/network/aaz/latest/network/watcher/flow_log/_update.py index 23ede57b4a7..6e15cc21643 100644 --- a/src/azure-cli/azure/cli/command_modules/network/aaz/latest/network/watcher/flow_log/_update.py +++ b/src/azure-cli/azure/cli/command_modules/network/aaz/latest/network/watcher/flow_log/_update.py @@ -37,12 +37,15 @@ class Update(AAZCommand): :example: Update Workspace on another resource group az network watcher flow-log update --location westus --resource-group MyAnotherResourceGroup --name MyFlowLog --workspace MyAnotherLogAnalyticWorkspace + + :example: Update flowlog with recordtypes filtering + az network watcher flow-log update --resource-group rg1 --network-watcher-name nw1 --name fl --location centraluseuap --target-resource-id /subscriptions/subid/resourceGroups/rg1/providers/Microsoft.Network/networkSecurityGroups/desmondcentral-nsg --storage-account /subscriptions/subid/resourceGroups/rg1/providers/Microsoft.Storage/storageAccounts/nwtest1mgvbfmqsigdxe --filtering-criteria srcIP=158.255.7.8 || dstPort=56891 --record-types B,E --enabled True --format JSON --log-version 1 --identity "{type:UserAssigned,user-assigned-identities:{/subscriptions/subid/resourceGroups/rg1/providers/Microsoft.ManagedIdentity/userAssignedIdentities/id1:{}}}" """ _aaz_info = { - "version": "2024-03-01", + "version": "2025-03-01", "resources": [ - ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.network/networkwatchers/{}/flowlogs/{}", "2024-03-01"], + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.network/networkwatchers/{}/flowlogs/{}", "2025-03-01"], ] } @@ -165,6 +168,12 @@ def _build_arguments_schema(cls, *args, **kwargs): help="Parameters that define the configuration of traffic analytics.", nullable=True, ) + _args_schema.record_types = AAZStrArg( + options=["--record-types"], + arg_group="Properties", + help="Optional field to filter network traffic logs based on flow states. Value of this field could be any comma separated combination string of letters B,C,E or D. B represents Begin, when a flow is created. C represents Continue for an ongoing flow generated at every five-minute interval. E represents End, when a flow is terminated. D represents Deny, when a flow is denied. If not specified, all network traffic will be logged.", + nullable=True, + ) _args_schema.retention_policy = AAZObjectArg( options=["--retention-policy"], arg_group="Properties", @@ -299,7 +308,7 @@ def url_parameters(self): def query_parameters(self): parameters = { **self.serialize_query_param( - "api-version", "2024-03-01", + "api-version", "2025-03-01", required=True, ), } @@ -402,7 +411,7 @@ def url_parameters(self): def query_parameters(self): parameters = { **self.serialize_query_param( - "api-version", "2024-03-01", + "api-version", "2025-03-01", required=True, ), } @@ -480,6 +489,7 @@ def _update_instance(self, instance): properties.set_prop("enabledFilteringCriteria", AAZStrType, ".filtering_criteria") properties.set_prop("flowAnalyticsConfiguration", AAZObjectType, ".flow_analytics_configuration") properties.set_prop("format", AAZObjectType) + properties.set_prop("recordTypes", AAZStrType, ".record_types") properties.set_prop("retentionPolicy", AAZObjectType, ".retention_policy") properties.set_prop("storageId", AAZStrType, ".storage_account", typ_kwargs={"flags": {"required": True}}) properties.set_prop("targetResourceId", AAZStrType, ".target_resource_id", typ_kwargs={"flags": {"required": True}}) @@ -599,6 +609,9 @@ def _build_schema_flow_log_read(cls, _schema): serialized_name="provisioningState", flags={"read_only": True}, ) + properties.record_types = AAZStrType( + serialized_name="recordTypes", + ) properties.retention_policy = AAZObjectType( serialized_name="retentionPolicy", ) diff --git a/src/azure-cli/azure/cli/command_modules/network/aaz/latest/network/watcher/flow_log/_wait.py b/src/azure-cli/azure/cli/command_modules/network/aaz/latest/network/watcher/flow_log/_wait.py index c6bf0b3c38e..5284e85cbc0 100644 --- a/src/azure-cli/azure/cli/command_modules/network/aaz/latest/network/watcher/flow_log/_wait.py +++ b/src/azure-cli/azure/cli/command_modules/network/aaz/latest/network/watcher/flow_log/_wait.py @@ -20,7 +20,7 @@ class Wait(AAZWaitCommand): _aaz_info = { "resources": [ - ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.network/networkwatchers/{}/flowlogs/{}", "2024-03-01"], + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.network/networkwatchers/{}/flowlogs/{}", "2025-03-01"], ] } @@ -126,7 +126,7 @@ def url_parameters(self): def query_parameters(self): parameters = { **self.serialize_query_param( - "api-version", "2024-03-01", + "api-version", "2025-03-01", required=True, ), } @@ -163,6 +163,7 @@ def _build_schema_on_200(cls): flags={"read_only": True}, ) _schema_on_200.id = AAZStrType() + _schema_on_200.identity = AAZIdentityObjectType() _schema_on_200.location = AAZStrType() _schema_on_200.name = AAZStrType( flags={"read_only": True}, @@ -175,6 +176,33 @@ def _build_schema_on_200(cls): flags={"read_only": True}, ) + identity = cls._schema_on_200.identity + identity.principal_id = AAZStrType( + serialized_name="principalId", + flags={"read_only": True}, + ) + identity.tenant_id = AAZStrType( + serialized_name="tenantId", + flags={"read_only": True}, + ) + identity.type = AAZStrType() + identity.user_assigned_identities = AAZDictType( + serialized_name="userAssignedIdentities", + ) + + user_assigned_identities = cls._schema_on_200.identity.user_assigned_identities + user_assigned_identities.Element = AAZObjectType() + + _element = cls._schema_on_200.identity.user_assigned_identities.Element + _element.client_id = AAZStrType( + serialized_name="clientId", + flags={"read_only": True}, + ) + _element.principal_id = AAZStrType( + serialized_name="principalId", + flags={"read_only": True}, + ) + properties = cls._schema_on_200.properties properties.enabled = AAZBoolType() properties.enabled_filtering_criteria = AAZStrType( @@ -188,6 +216,9 @@ def _build_schema_on_200(cls): serialized_name="provisioningState", flags={"read_only": True}, ) + properties.record_types = AAZStrType( + serialized_name="recordTypes", + ) properties.retention_policy = AAZObjectType( serialized_name="retentionPolicy", ) diff --git a/src/azure-cli/azure/cli/command_modules/network/tests/latest/test_nw_flow_log.py b/src/azure-cli/azure/cli/command_modules/network/tests/latest/test_nw_flow_log.py index e1df3d3f2f4..c36fa292c12 100644 --- a/src/azure-cli/azure/cli/command_modules/network/tests/latest/test_nw_flow_log.py +++ b/src/azure-cli/azure/cli/command_modules/network/tests/latest/test_nw_flow_log.py @@ -163,6 +163,137 @@ def test_nw_flow_log_create_vnetfl_with_filtering(self, resource_group, resource self.check('retentionPolicy.enabled', False), ]) + @ResourceGroupPreparer(name_prefix='test_nw_flow_log_', location='centraluseuap') + @StorageAccountPreparer(name_prefix='testflowlog', location='centraluseuap', kind='StorageV2') + def test_nw_flow_log_create_vnetfl_with_record_types(self, resource_group, resource_group_location, storage_account): + self.kwargs.update({ + 'rg': resource_group, + 'location': resource_group_location, + 'storage_account': storage_account, + 'vnet': 'vnet1', + 'subnet': 'subnet1', + 'nic': 'nic1', + 'watcher_rg': 'NetworkWatcherRG', + 'watcher_name': 'NetworkWatcher_{}'.format(resource_group_location), + 'flow_log': 'flow_log_test', + 'record_types': 'B,E', + 'workspace': self.create_random_name('clitest', 20), + }) + + # enable network watcher + # self.cmd('network watcher configure -g {rg} --locations {location} --enabled') + + # prepare the target resource + self.cmd('network vnet create -g {rg} -n {vnet}') + self.cmd('network vnet subnet create -g {rg} --vnet-name {vnet} -n {subnet} --address-prefix 10.0.0.0/24') + self.cmd('network nic create -g {rg} -n {nic} --vnet-name {vnet} --subnet {subnet}') + + # prepare workspace + workspace = self.cmd('monitor log-analytics workspace create ' + '--resource-group {rg} ' + '--location eastus ' + '--workspace-name {workspace} ').get_output_in_json() + self.kwargs.update({ + 'workspace_id': workspace['id'] + }) + + # targetId as vnet + self.cmd('network watcher flow-log create ' + '--location {location} ' + '--resource-group {rg} ' + '--vnet {vnet} ' + '--storage-account {storage_account} ' + '--record-types {record_types} ' + '--workspace {workspace_id} ' + '--name {flow_log} ') + + self.cmd('network watcher flow-log list --location {location}') + + # This output is Azure Management Resource formatted. + self.cmd('network watcher flow-log show --location {location} --name {flow_log}', checks=[ + self.check('name', self.kwargs['flow_log']), + self.check('recordTypes', '{record_types}'), + self.check('flowAnalyticsConfiguration.networkWatcherFlowAnalyticsConfiguration.workspaceResourceId', + self.kwargs['workspace_id']), + self.check_pattern('targetResourceId', '.*/{vnet}$'), + self.check('retentionPolicy.days', 0), + self.check('retentionPolicy.enabled', False), + ]) + + @ResourceGroupPreparer(name_prefix='test_nw_flow_log_', location='centraluseuap') + @StorageAccountPreparer(name_prefix='testflowlog', location='centraluseuap', kind='StorageV2') + def test_nw_flow_log_update_vnetfl_with_record_types(self, resource_group, resource_group_location, storage_account): + self.kwargs.update({ + 'rg': resource_group, + 'location': resource_group_location, + 'storage_account': storage_account, + 'vnet': 'vnet1', + 'subnet': 'subnet1', + 'nic': 'nic1', + 'watcher_rg': 'NetworkWatcherRG', + 'watcher_name': 'NetworkWatcher_{}'.format(resource_group_location), + 'flow_log': 'flow_log_test_update_rt', + 'workspace': self.create_random_name('clitest', 20), + 'record_types': 'B,E' + }) + + # Prepare target resources + self.cmd('network vnet create -g {rg} -n {vnet}') + self.cmd('network vnet subnet create -g {rg} --vnet-name {vnet} -n {subnet} --address-prefix 10.0.0.0/24') + self.cmd('network nic create -g {rg} -n {nic} --vnet-name {vnet} --subnet {subnet}') + + # Prepare workspace (use eastus for LA workspace) + workspace = self.cmd( + 'monitor log-analytics workspace create ' + '--resource-group {rg} ' + '--location eastus ' + '--workspace-name {workspace} ' + ).get_output_in_json() + self.kwargs.update({'workspace_id': workspace['id']}) + + # Create flow log for VNet (no record-types initially) + self.cmd( + 'network watcher flow-log create ' + '--location {location} ' + '--resource-group {rg} ' + '--vnet {vnet} ' + '--storage-account {storage_account} ' + '--workspace {workspace_id} ' + '--name {flow_log} ' + ) + + # Baseline verification + res1 = self.cmd( + 'network watcher flow-log show --location {location} --name {flow_log}' + ).get_output_in_json() + self.assertEqual(res1['name'], self.kwargs['flow_log']) + self.assertEqual(res1['enabled'], True) + self.assertTrue(res1['targetResourceId'].endswith(self.kwargs['vnet'])) + + # ---- Update: set record types to B,E on the same VNet target ---- + res2 = self.cmd( + 'network watcher flow-log update ' + '--location {location} ' + '--resource-group {rg} ' + '--vnet {vnet} ' + '--name {flow_log} ' + '--record-types {record_types} ' + ).get_output_in_json() + + self.assertEqual(res2['name'], self.kwargs['flow_log']) + self.assertEqual(res2['enabled'], True) + self.assertTrue(res2['targetResourceId'].endswith(self.kwargs['vnet'])) + self.assertEqual(res2['recordTypes'], self.kwargs['record_types']) + # Retention defaults remain unless explicitly changed + self.assertEqual(res2['retentionPolicy']['enabled'], False) + self.assertEqual(res2['retentionPolicy']['days'], 0) + + # Show again and validate persisted update + self.cmd('network watcher flow-log show --location {location} --name {flow_log}', checks=[ + self.check('name', self.kwargs['flow_log']), + self.check('recordTypes', '{record_types}') + ]) + @ResourceGroupPreparer(name_prefix='test_nw_flow_log_', location='centraluseuap') @StorageAccountPreparer(name_prefix='testflowlog', location='centraluseuap', kind='StorageV2') def test_nw_flow_log_create_vnetflWithManagedIdentity(self, resource_group, resource_group_location, storage_account):