1# --------------------------------------------------------------------------------------------
2# Copyright (c) Microsoft Corporation. All rights reserved.
3# Licensed under the MIT License. See License.txt in the project root for license information.
4# --------------------------------------------------------------------------------------------
5# pylint: disable=unused-argument
6from azure.mgmt.synapse.models import BlobAuditingPolicyState
7from azure.cli.core.commands.client_factory import get_mgmt_service_client
8from azure.cli.core.profiles import ResourceType
9from azure.cli.command_modules.monitor.operations.diagnostics_settings import create_diagnostics_settings
10from azure.cli.command_modules.monitor._client_factory import cf_monitor
11from knack.util import CLIError
12from knack.log import get_logger
13
14
15logger = get_logger(__name__)
16
17
18def sqlpool_blob_auditing_policy_update(
19        cmd,
20        instance,
21        workspace_name,
22        resource_group_name,
23        sql_pool_name,
24        state=None,
25        blob_storage_target_state=None,
26        storage_account=None,
27        storage_endpoint=None,
28        storage_account_access_key=None,
29        storage_account_subscription_id=None,
30        is_storage_secondary_key_in_use=None,
31        retention_days=None,
32        audit_actions_and_groups=None,
33        log_analytics_target_state=None,
34        log_analytics_workspace_resource_id=None,
35        event_hub_target_state=None,
36        event_hub_authorization_rule_id=None,
37        event_hub=None,
38        is_azure_monitor_target_enabled=None,
39        blob_auditing_policy_name=None):
40    """
41    Updates a sql pool blob auditing policy. Custom update function to apply parameters to instance.
42    """
43
44    _audit_policy_update(
45        cmd=cmd,
46        instance=instance,
47        workspace_name=workspace_name,
48        resource_group_name=resource_group_name,
49        sql_pool_name=sql_pool_name,
50        state=state,
51        blob_storage_target_state=blob_storage_target_state,
52        storage_account=storage_account,
53        storage_endpoint=storage_endpoint,
54        storage_account_access_key=storage_account_access_key,
55        storage_account_subscription_id=storage_account_subscription_id,
56        is_storage_secondary_key_in_use=is_storage_secondary_key_in_use,
57        retention_days=retention_days,
58        category_name='SQLSecurityAuditEvents',
59        log_analytics_target_state=log_analytics_target_state,
60        log_analytics_workspace_resource_id=log_analytics_workspace_resource_id,
61        event_hub_target_state=event_hub_target_state,
62        event_hub_authorization_rule_id=event_hub_authorization_rule_id,
63        event_hub_name=event_hub,
64        audit_actions_and_groups=audit_actions_and_groups,
65        is_azure_monitor_target_enabled=is_azure_monitor_target_enabled)
66    return instance
67
68
69def sqlserver_blob_auditing_policy_update(
70        cmd,
71        instance,
72        workspace_name,
73        resource_group_name,
74        state=None,
75        blob_storage_target_state=None,
76        storage_account=None,
77        storage_endpoint=None,
78        storage_account_access_key=None,
79        retention_days=None,
80        audit_actions_and_groups=None,
81        log_analytics_target_state=None,
82        log_analytics_workspace_resource_id=None,
83        event_hub_target_state=None,
84        event_hub_authorization_rule_id=None,
85        event_hub=None,
86        storage_account_subscription_id=None,
87        is_storage_secondary_key_in_use=None,
88        is_azure_monitor_target_enabled=None,
89        queue_delay_milliseconds=None,
90        blob_auditing_policy_name=None):
91    _audit_policy_update(
92        cmd=cmd,
93        instance=instance,
94        state=state,
95        workspace_name=workspace_name,
96        resource_group_name=resource_group_name,
97        blob_storage_target_state=blob_storage_target_state,
98        storage_account=storage_account,
99        storage_endpoint=storage_endpoint,
100        storage_account_access_key=storage_account_access_key,
101        audit_actions_and_groups=audit_actions_and_groups,
102        retention_days=retention_days,
103        category_name='SQLSecurityAuditEvents',
104        log_analytics_target_state=log_analytics_target_state,
105        log_analytics_workspace_resource_id=log_analytics_workspace_resource_id,
106        event_hub_target_state=event_hub_target_state,
107        event_hub_authorization_rule_id=event_hub_authorization_rule_id,
108        storage_account_subscription_id=storage_account_subscription_id,
109        event_hub_name=event_hub,
110        is_storage_secondary_key_in_use=is_storage_secondary_key_in_use,
111        is_azure_monitor_target_enabled=is_azure_monitor_target_enabled)
112
113    # this property is only for ServerBlobAuditingPolicy
114    if queue_delay_milliseconds is not None:
115        instance.queue_delay_ms = queue_delay_milliseconds
116
117    return instance
118
119
120def _audit_policy_update(
121        cmd,
122        instance,
123        workspace_name,
124        resource_group_name,
125        sql_pool_name=None,
126        state=None,
127        blob_storage_target_state=None,
128        storage_account=None,
129        storage_endpoint=None,
130        storage_account_access_key=None,
131        storage_account_subscription_id=None,
132        is_storage_secondary_key_in_use=None,
133        retention_days=None,
134        category_name=None,
135        log_analytics_target_state=None,
136        log_analytics_workspace_resource_id=None,
137        event_hub_target_state=None,
138        event_hub_authorization_rule_id=None,
139        event_hub_name=None,
140        audit_actions_and_groups=None,
141        is_azure_monitor_target_enabled=None):
142
143    # Arguments validation
144    _audit_policy_validate_arguments(
145        state=state,
146        blob_storage_target_state=blob_storage_target_state,
147        storage_account=storage_account,
148        storage_endpoint=storage_endpoint,
149        storage_account_access_key=storage_account_access_key,
150        retention_days=retention_days,
151        log_analytics_target_state=log_analytics_target_state,
152        log_analytics_workspace_resource_id=log_analytics_workspace_resource_id,
153        event_hub_target_state=event_hub_target_state,
154        event_hub_authorization_rule_id=event_hub_authorization_rule_id,
155        event_hub_name=event_hub_name)
156
157    # Get diagnostic settings only if log_analytics_target_state or event_hub_target_state is provided
158    if log_analytics_target_state is not None or event_hub_target_state is not None:
159        diagnostic_settings = _get_diagnostic_settings(
160            cmd=cmd,
161            resource_group_name=resource_group_name,
162            workspace_name=workspace_name,
163            sql_pool_name=sql_pool_name)
164
165        # Update diagnostic settings
166        rollback_data = _audit_policy_update_diagnostic_settings(
167            cmd=cmd,
168            workspace_name=workspace_name,
169            resource_group_name=resource_group_name,
170            sql_pool_name=sql_pool_name,
171            diagnostic_settings=diagnostic_settings,
172            category_name=category_name,
173            log_analytics_target_state=log_analytics_target_state,
174            log_analytics_workspace_resource_id=log_analytics_workspace_resource_id,
175            event_hub_target_state=event_hub_target_state,
176            event_hub_authorization_rule_id=event_hub_authorization_rule_id,
177            event_hub_name=event_hub_name)
178    else:
179        diagnostic_settings = None
180        rollback_data = None
181
182    try:
183        # Apply State
184        if state is not None:
185            instance.state = BlobAuditingPolicyState[state.lower()]
186
187        # Apply additional command line arguments only if policy's state is enabled
188        if _is_audit_policy_state_enabled(instance.state):
189            if is_storage_secondary_key_in_use is not None:
190                instance.is_storage_secondary_key_in_use = is_storage_secondary_key_in_use
191            if is_azure_monitor_target_enabled is not None:
192                instance.is_azure_monitor_target_enabled = is_azure_monitor_target_enabled
193
194            # handle storage related parameters
195            _audit_policy_update_apply_blob_storage_details(
196                cmd=cmd,
197                instance=instance,
198                blob_storage_target_state=blob_storage_target_state,
199                storage_account=storage_account,
200                storage_endpoint=storage_endpoint,
201                storage_account_access_key=storage_account_access_key,
202                retention_days=retention_days,
203                storage_account_subscription_id=storage_account_subscription_id)
204
205            if audit_actions_and_groups is not None:
206                instance.audit_actions_and_groups = audit_actions_and_groups
207
208            if not instance.audit_actions_and_groups or instance.audit_actions_and_groups == []:
209                instance.audit_actions_and_groups = [
210                    "SUCCESSFUL_DATABASE_AUTHENTICATION_GROUP",
211                    "FAILED_DATABASE_AUTHENTICATION_GROUP",
212                    "BATCH_COMPLETED_GROUP"]
213
214            # Apply is_azure_monitor_target_enabled
215            _audit_policy_update_apply_azure_monitor_target_enabled(
216                instance=instance,
217                diagnostic_settings=diagnostic_settings,
218                category_name=category_name,
219                log_analytics_target_state=log_analytics_target_state,
220                event_hub_target_state=event_hub_target_state)
221        return instance
222    except Exception as err:
223        logger.debug(err)
224
225        if rollback_data is not None:
226            _audit_policy_update_rollback(
227                cmd=cmd,
228                workspace_name=workspace_name,
229                resource_group_name=resource_group_name,
230                sql_pool_name=sql_pool_name,
231                rollback_data=rollback_data)
232
233        # Reraise the original exception
234        raise err
235
236
237def _audit_policy_update_apply_blob_storage_details(
238        cmd,
239        instance,
240        blob_storage_target_state,
241        retention_days,
242        storage_account,
243        storage_account_access_key,
244        storage_endpoint,
245        storage_account_subscription_id):
246    '''
247        Apply blob storage details on policy update
248        '''
249    if hasattr(instance, 'is_storage_secondary_key_in_use'):
250        is_storage_secondary_key_in_use = instance.is_storage_secondary_key_in_use
251    else:
252        is_storage_secondary_key_in_use = False
253    if blob_storage_target_state is None:
254        # Original audit policy has no storage_endpoint
255        if not instance.storage_endpoint:
256            instance.storage_endpoint = None
257            instance.storage_account_access_key = None
258        else:
259            # Resolve storage_account_access_key based on original storage_endpoint
260            storage_account = _get_storage_account_name(instance.storage_endpoint)
261            storage_resource_group = _find_storage_account_resource_group(cmd.cli_ctx, storage_account)
262
263            instance.storage_account_access_key = _get_storage_key(
264                cli_ctx=cmd.cli_ctx,
265                storage_account=storage_account,
266                resource_group_name=storage_resource_group,
267                use_secondary_key=is_storage_secondary_key_in_use)
268    elif _is_audit_policy_state_enabled(blob_storage_target_state):
269        if storage_account is not None:
270            storage_resource_group = _find_storage_account_resource_group(cmd.cli_ctx, storage_account)
271            storage_endpoint = _get_storage_endpoint(cmd.cli_ctx, storage_account, storage_resource_group)
272            storage_account_subscription_id = _find_storage_account_subscription_id(cmd.cli_ctx, storage_account)
273
274        if storage_endpoint is not None:
275            instance.storage_endpoint = storage_endpoint
276
277        if storage_account_subscription_id is not None:
278            instance.storage_account_subscription_id = storage_account_subscription_id
279
280        if storage_account_access_key is not None:
281            instance.storage_account_access_key = storage_account_access_key
282        elif storage_endpoint is not None:
283            # Resolve storage_account if not provided
284            if storage_account is None:
285                storage_account = _get_storage_account_name(storage_endpoint)
286                storage_resource_group = _find_storage_account_resource_group(cmd.cli_ctx, storage_account)
287
288            # Resolve storage_account_access_key based on storage_account
289            instance.storage_account_access_key = _get_storage_key(
290                cli_ctx=cmd.cli_ctx,
291                storage_account=storage_account,
292                resource_group_name=storage_resource_group,
293                use_secondary_key=instance.is_storage_secondary_key_in_use)
294
295        if retention_days is not None:
296            instance.retention_days = retention_days
297
298    else:
299        instance.storage_endpoint = None
300        instance.storage_account_access_key = None
301
302
303def _find_storage_account_resource_id(cli_ctx, name):
304    '''
305    Finds a storage account's resource group by querying ARM resource cache.
306
307    Why do we have to do this: so we know the resource group in order to later query the storage API
308    to determine the account's keys and endpoint. Why isn't this just a command line parameter:
309    because if it was a command line parameter then the customer would need to specify storage
310    resource group just to update some unrelated property, which is annoying and makes no sense to
311    the customer.
312    '''
313
314    storage_type = 'Microsoft.Storage/storageAccounts'
315    classic_storage_type = 'Microsoft.ClassicStorage/storageAccounts'
316
317    query = "name eq '{}' and (resourceType eq '{}' or resourceType eq '{}')".format(
318        name, storage_type, classic_storage_type)
319
320    client = get_mgmt_service_client(cli_ctx, ResourceType.MGMT_RESOURCE_RESOURCES)
321    resources = list(client.resources.list(filter=query))
322
323    if not resources:
324        raise CLIError("No storage account with name '{}' was found.".format(name))
325
326    if len(resources) > 1:
327        raise CLIError("Multiple storage accounts with name '{}' were found.".format(name))
328
329    if resources[0].type == classic_storage_type:
330        raise CLIError("The storage account with name '{}' is a classic storage account which is"
331                       " not supported by this command. Use a non-classic storage account or"
332                       " specify storage endpoint and key instead.".format(name))
333
334    return resources[0].id
335
336
337def _find_storage_account_resource_group(cli_ctx, name):
338    """
339    Finds a storage account's resource group by querying ARM resource cache.
340
341    Why do we have to do this: so we know the resource group in order to later query the storage API
342    to determine the account's keys and endpoint. Why isn't this just a command line parameter:
343    because if it was a command line parameter then the customer would need to specify storage
344    resource group just to update some unrelated property, which is annoying and makes no sense to
345    the customer.
346    """
347    resource_id = _find_storage_account_resource_id(cli_ctx, name)
348    # Split the uri and return just the resource group
349    return resource_id.split('/')[4]
350
351
352def _find_storage_account_subscription_id(cli_ctx, name):
353    """
354    Finds a storage account's resource group by querying ARM resource cache.
355
356    Why do we have to do this: so we know the resource group in order to later query the storage API
357    to determine the account's keys and endpoint. Why isn't this just a command line parameter:
358    because if it was a command line parameter then the customer would need to specify storage
359    resource group just to update some unrelated property, which is annoying and makes no sense to
360    the customer.
361    """
362    resource_id = _find_storage_account_resource_id(cli_ctx, name)
363    # Split the uri and return just the resource group
364    return resource_id.split('/')[2]
365
366
367def _get_storage_account_name(storage_endpoint):
368    """
369    Determines storage account name from endpoint url string.
370    e.g. 'https://mystorage.blob.core.windows.net' -> 'mystorage'
371    """
372    # url parse package has different names in Python 2 and 3. 'six' package works cross-version.
373    from six.moves.urllib.parse import urlparse  # pylint: disable=import-error
374
375    return urlparse(storage_endpoint).netloc.split('.')[0]
376
377
378def _get_storage_endpoint(
379        cli_ctx,
380        storage_account,
381        resource_group_name):
382    """
383    Gets storage account endpoint by querying storage ARM API.
384    """
385    from azure.mgmt.storage import StorageManagementClient
386
387    # Get storage account
388    client = get_mgmt_service_client(cli_ctx, StorageManagementClient)
389    account = client.storage_accounts.get_properties(
390        resource_group_name=resource_group_name,
391        account_name=storage_account)
392
393    # Get endpoint
394    # pylint: disable=no-member
395    endpoints = account.primary_endpoints
396    try:
397        return endpoints.blob
398    except AttributeError:
399        raise CLIError("The storage account with name '{}' (id '{}') has no blob endpoint. Use a"
400                       " different storage account.".format(account.name, account.id))
401
402
403def _get_storage_key(
404        cli_ctx,
405        storage_account,
406        resource_group_name,
407        use_secondary_key):
408    """
409    Gets storage account key by querying storage ARM API.
410    """
411    from azure.mgmt.storage import StorageManagementClient
412
413    # Get storage keys
414    client = get_mgmt_service_client(cli_ctx, StorageManagementClient)
415    keys = client.storage_accounts.list_keys(
416        resource_group_name=resource_group_name,
417        account_name=storage_account)
418
419    # Choose storage key
420    index = 1 if use_secondary_key else 0
421    return keys.keys[index].value  # pylint: disable=no-member
422
423
424def _check_audit_policy_state(
425        state,
426        value):
427    return state is not None and state.lower() == value.lower()
428
429
430def _is_audit_policy_state_enabled(state):
431    return _check_audit_policy_state(state, BlobAuditingPolicyState.enabled.value)
432
433
434def _is_audit_policy_state_none_or_disabled(state):
435    return state is None or _check_audit_policy_state(state, BlobAuditingPolicyState.disabled.value)
436
437
438def _fetch_first_audit_diagnostic_setting(diagnostic_settings, category_name):
439    return next((ds for ds in diagnostic_settings if hasattr(ds, 'logs') and
440                 next((log for log in ds.logs if log.enabled and
441                       log.category == category_name), None) is not None), None)
442
443
444def _fetch_all_audit_diagnostic_settings(diagnostic_settings, category_name):
445    return [ds for ds in diagnostic_settings if hasattr(ds, 'logs') and
446            next((log for log in ds.logs if log.enabled and
447                  log.category == category_name), None) is not None]
448
449
450def _get_diagnostic_settings(
451        cmd,
452        resource_group_name,
453        workspace_name,
454        sql_pool_name=None):
455    '''
456    Common code to get workspace or sqlpool diagnostic settings
457    '''
458
459    diagnostic_settings_url = _get_diagnostic_settings_url(
460        cmd=cmd, resource_group_name=resource_group_name,
461        workspace_name=workspace_name, sql_pool_name=sql_pool_name)
462    azure_monitor_client = cf_monitor(cmd.cli_ctx)
463    return azure_monitor_client.diagnostic_settings.list(diagnostic_settings_url)
464
465
466def _get_diagnostic_settings_url(
467        cmd,
468        resource_group_name,
469        workspace_name,
470        sql_pool_name=None):
471
472    from azure.cli.core.commands.client_factory import get_subscription_id
473
474    diagnostic_settings_url = '/subscriptions/{}/resourceGroups/{}/providers/Microsoft.Synapse/workspaces/{}'.format(
475        get_subscription_id(cmd.cli_ctx), resource_group_name, workspace_name)
476    if sql_pool_name is not None:
477        return diagnostic_settings_url + '/sqlpools/{}'.format(sql_pool_name)
478    return diagnostic_settings_url
479
480
481def _audit_policy_update_rollback(
482        cmd,
483        workspace_name,
484        resource_group_name,
485        sql_pool_name,
486        rollback_data):
487    '''
488    Rollback diagnostic settings change
489    '''
490
491    diagnostic_settings_url = _get_diagnostic_settings_url(
492        cmd=cmd,
493        resource_group_name=resource_group_name,
494        workspace_name=workspace_name,
495        sql_pool_name=sql_pool_name)
496
497    azure_monitor_client = cf_monitor(cmd.cli_ctx)
498
499    for rd in rollback_data:
500        rollback_diagnostic_setting = rd[1]
501
502        if rd[0] == "create" or rd[0] == "update":
503            create_diagnostics_settings(
504                client=azure_monitor_client.diagnostic_settings,
505                name=rollback_diagnostic_setting.name,
506                resource_uri=diagnostic_settings_url,
507                logs=rollback_diagnostic_setting.logs,
508                metrics=rollback_diagnostic_setting.metrics,
509                event_hub=rollback_diagnostic_setting.event_hub_name,
510                event_hub_rule=rollback_diagnostic_setting.event_hub_authorization_rule_id,
511                storage_account=rollback_diagnostic_setting.storage_account_id,
512                workspace=rollback_diagnostic_setting.workspace_id)
513        else:  # delete
514            azure_monitor_client.diagnostic_settings.delete(diagnostic_settings_url, rollback_diagnostic_setting.name)
515
516
517def _audit_policy_update_apply_azure_monitor_target_enabled(
518        instance,
519        diagnostic_settings,
520        category_name,
521        log_analytics_target_state,
522        event_hub_target_state):
523    '''
524    Apply value of is_azure_monitor_target_enabled on policy update
525    '''
526
527    # If log_analytics_target_state and event_hub_target_state are None there is nothing to do
528    if log_analytics_target_state is None and event_hub_target_state is None:
529        return
530
531    if _is_audit_policy_state_enabled(log_analytics_target_state) or\
532            _is_audit_policy_state_enabled(event_hub_target_state):
533        instance.is_azure_monitor_target_enabled = True
534    else:
535        # Sort received diagnostic settings by name and get first element to ensure consistency
536        # between command executions
537        diagnostic_settings.value.sort(key=lambda d: d.name)
538        audit_diagnostic_setting = _fetch_first_audit_diagnostic_setting(diagnostic_settings.value, category_name)
539
540        # Determine value of is_azure_monitor_target_enabled
541        if audit_diagnostic_setting is None:
542            updated_log_analytics_workspace_id = None
543            updated_event_hub_authorization_rule_id = None
544        else:
545            updated_log_analytics_workspace_id = audit_diagnostic_setting.workspace_id
546            updated_event_hub_authorization_rule_id = audit_diagnostic_setting.event_hub_authorization_rule_id
547
548        if _is_audit_policy_state_disabled(log_analytics_target_state):
549            updated_log_analytics_workspace_id = None
550
551        if _is_audit_policy_state_disabled(event_hub_target_state):
552            updated_event_hub_authorization_rule_id = None
553
554        instance.is_azure_monitor_target_enabled = updated_log_analytics_workspace_id is not None or\
555            updated_event_hub_authorization_rule_id is not None
556
557
558def _is_audit_policy_state_disabled(state):
559    return _check_audit_policy_state(state, BlobAuditingPolicyState.disabled.value)
560
561
562def _audit_policy_update_diagnostic_settings(
563        cmd,
564        workspace_name,
565        resource_group_name,
566        sql_pool_name=None,
567        diagnostic_settings=None,
568        category_name=None,
569        log_analytics_target_state=None,
570        log_analytics_workspace_resource_id=None,
571        event_hub_target_state=None,
572        event_hub_authorization_rule_id=None,
573        event_hub_name=None):
574    '''
575    Update audit policy's diagnostic settings
576    '''
577
578    # Fetch all audit diagnostic settings
579    audit_diagnostic_settings = _fetch_all_audit_diagnostic_settings(diagnostic_settings.value, category_name)
580    num_of_audit_diagnostic_settings = len(audit_diagnostic_settings)
581
582    # If more than 1 audit diagnostic settings found then throw error
583    if num_of_audit_diagnostic_settings > 1:
584        raise CLIError('Multiple audit diagnostics settings are already enabled')
585
586    diagnostic_settings_url = _get_diagnostic_settings_url(
587        cmd=cmd,
588        resource_group_name=resource_group_name,
589        workspace_name=workspace_name,
590        sql_pool_name=sql_pool_name)
591
592    azure_monitor_client = cf_monitor(cmd.cli_ctx)
593
594    # If no audit diagnostic settings found then create one if azure monitor is enabled
595    if num_of_audit_diagnostic_settings == 0:
596        if _is_audit_policy_state_enabled(log_analytics_target_state) or\
597                _is_audit_policy_state_enabled(event_hub_target_state):
598            created_diagnostic_setting = _audit_policy_create_diagnostic_setting(
599                cmd=cmd,
600                resource_group_name=resource_group_name,
601                workspace_name=workspace_name,
602                sql_pool_name=sql_pool_name,
603                category_name=category_name,
604                log_analytics_target_state=log_analytics_target_state,
605                log_analytics_workspace_resource_id=log_analytics_workspace_resource_id,
606                event_hub_target_state=event_hub_target_state,
607                event_hub_authorization_rule_id=event_hub_authorization_rule_id,
608                event_hub_name=event_hub_name)
609
610            # Return rollback data tuple
611            return [("delete", created_diagnostic_setting)]
612
613        # azure monitor is disabled - there is nothing to do
614        return None
615
616    # This leaves us with case when num_of_audit_diagnostic_settings is 1
617    audit_diagnostic_setting = audit_diagnostic_settings[0]
618
619    # Initialize actually updated azure monitor fields
620    if log_analytics_target_state is None:
621        log_analytics_workspace_resource_id = audit_diagnostic_setting.workspace_id
622    elif _is_audit_policy_state_disabled(log_analytics_target_state):
623        log_analytics_workspace_resource_id = None
624
625    if event_hub_target_state is None:
626        event_hub_authorization_rule_id = audit_diagnostic_setting.event_hub_authorization_rule_id
627        event_hub_name = audit_diagnostic_setting.event_hub_name
628    elif _is_audit_policy_state_disabled(event_hub_target_state):
629        event_hub_authorization_rule_id = None
630        event_hub_name = None
631
632    is_azure_monitor_target_enabled = log_analytics_workspace_resource_id is not None or\
633        event_hub_authorization_rule_id is not None
634
635    has_other_categories = next((log for log in audit_diagnostic_setting.logs
636                                 if log.enabled and log.category != category_name), None) is not None
637
638    # If there is no other categories except SQLSecurityAuditEvents\DevOpsOperationsAudit update or delete
639    # the existing single diagnostic settings
640    if not has_other_categories:
641        # If azure monitor is enabled then update existing single audit diagnostic setting
642        if is_azure_monitor_target_enabled:
643            create_diagnostics_settings(
644                client=azure_monitor_client.diagnostic_settings,
645                name=audit_diagnostic_setting.name,
646                resource_uri=diagnostic_settings_url,
647                logs=audit_diagnostic_setting.logs,
648                metrics=audit_diagnostic_setting.metrics,
649                event_hub=event_hub_name,
650                event_hub_rule=event_hub_authorization_rule_id,
651                storage_account=audit_diagnostic_setting.storage_account_id,
652                workspace=log_analytics_workspace_resource_id)
653
654            # Return rollback data tuple
655            return [("update", audit_diagnostic_setting)]
656
657        # Azure monitor is disabled, delete existing single audit diagnostic setting
658        azure_monitor_client.diagnostic_settings.delete(diagnostic_settings_url, audit_diagnostic_setting.name)
659
660        # Return rollback data tuple
661        return [("create", audit_diagnostic_setting)]
662
663    # In case there are other categories in the existing single audit diagnostic setting a "split" must be performed:
664    #   1. Disable SQLSecurityAuditEvents\DevOpsOperationsAudit category in found audit diagnostic setting
665    #   2. Create new diagnostic setting with SQLSecurityAuditEvents\DevOpsOperationsAudit category,
666    #      i.e. audit diagnostic setting
667
668    # Build updated logs list with disabled SQLSecurityAuditEvents\DevOpsOperationsAudit category
669    updated_logs = []
670
671    LogSettings = cmd.get_models(
672        'LogSettings',
673        resource_type=ResourceType.MGMT_MONITOR,
674        operation_group='diagnostic_settings')
675
676    RetentionPolicy = cmd.get_models(
677        'RetentionPolicy',
678        resource_type=ResourceType.MGMT_MONITOR,
679        operation_group='diagnostic_settings')
680
681    for log in audit_diagnostic_setting.logs:
682        if log.category == category_name:
683            updated_logs.append(LogSettings(category=log.category, enabled=False,
684                                            retention_policy=RetentionPolicy(enabled=False, days=0)))
685        else:
686            updated_logs.append(log)
687
688    # Update existing diagnostic settings
689    create_diagnostics_settings(
690        client=azure_monitor_client.diagnostic_settings,
691        name=audit_diagnostic_setting.name,
692        resource_uri=diagnostic_settings_url,
693        logs=updated_logs,
694        metrics=audit_diagnostic_setting.metrics,
695        event_hub=audit_diagnostic_setting.event_hub_name,
696        event_hub_rule=audit_diagnostic_setting.event_hub_authorization_rule_id,
697        storage_account=audit_diagnostic_setting.storage_account_id,
698        workspace=audit_diagnostic_setting.workspace_id)
699
700    # Add original 'audit_diagnostic_settings' to rollback_data list
701    rollback_data = [("update", audit_diagnostic_setting)]
702
703    # Create new diagnostic settings with enabled SQLSecurityAuditEvents\DevOpsOperationsAudit category
704    # only if azure monitor is enabled
705    if is_azure_monitor_target_enabled:
706        created_diagnostic_setting = _audit_policy_create_diagnostic_setting(
707            cmd=cmd,
708            resource_group_name=resource_group_name,
709            workspace_name=workspace_name,
710            sql_pool_name=sql_pool_name,
711            category_name=category_name,
712            log_analytics_target_state=log_analytics_target_state,
713            log_analytics_workspace_resource_id=log_analytics_workspace_resource_id,
714            event_hub_target_state=event_hub_target_state,
715            event_hub_authorization_rule_id=event_hub_authorization_rule_id,
716            event_hub_name=event_hub_name)
717
718        # Add 'created_diagnostic_settings' to rollback_data list in reverse order
719        rollback_data.insert(0, ("delete", created_diagnostic_setting))
720
721    return rollback_data
722
723
724def _audit_policy_create_diagnostic_setting(
725        cmd,
726        resource_group_name,
727        workspace_name,
728        sql_pool_name=None,
729        category_name=None,
730        log_analytics_target_state=None,
731        log_analytics_workspace_resource_id=None,
732        event_hub_target_state=None,
733        event_hub_authorization_rule_id=None,
734        event_hub_name=None):
735    '''
736    Create audit diagnostic setting, i.e. containing single category - SQLSecurityAuditEvents or DevOpsOperationsAudit
737    '''
738
739    # Generate diagnostic settings name to be created
740    name = category_name
741
742    import inspect
743    test_methods = ["test_sql_ws_audit_policy_logentry_eventhub", "test_sql_pool_audit_policy_logentry_eventhub"]
744    test_mode = next((e for e in inspect.stack() if e.function in test_methods), None) is not None
745
746    # For test environment the name should be constant, i.e. match the name written in recorded yaml file
747    if test_mode:
748        name += '_LogAnalytics' if log_analytics_target_state is not None else ''
749        name += '_EventHub' if event_hub_target_state is not None else ''
750    else:
751        import uuid
752        name += '_' + str(uuid.uuid4())
753
754    diagnostic_settings_url = _get_diagnostic_settings_url(
755        cmd=cmd,
756        resource_group_name=resource_group_name,
757        workspace_name=workspace_name,
758        sql_pool_name=sql_pool_name)
759
760    azure_monitor_client = cf_monitor(cmd.cli_ctx)
761
762    LogSettings = cmd.get_models(
763        'LogSettings',
764        resource_type=ResourceType.MGMT_MONITOR,
765        operation_group='diagnostic_settings')
766
767    RetentionPolicy = cmd.get_models(
768        'RetentionPolicy',
769        resource_type=ResourceType.MGMT_MONITOR,
770        operation_group='diagnostic_settings')
771
772    return create_diagnostics_settings(
773        client=azure_monitor_client.diagnostic_settings,
774        name=name,
775        resource_uri=diagnostic_settings_url,
776        logs=[LogSettings(category=category_name, enabled=True,
777                          retention_policy=RetentionPolicy(enabled=False, days=0))],
778        metrics=None,
779        event_hub=event_hub_name,
780        event_hub_rule=event_hub_authorization_rule_id,
781        storage_account=None,
782        workspace=log_analytics_workspace_resource_id)
783
784
785def _audit_policy_validate_arguments(
786        state=None,
787        blob_storage_target_state=None,
788        storage_account=None,
789        storage_endpoint=None,
790        storage_account_access_key=None,
791        retention_days=None,
792        log_analytics_target_state=None,
793        log_analytics_workspace_resource_id=None,
794        event_hub_target_state=None,
795        event_hub_authorization_rule_id=None,
796        event_hub_name=None):
797    '''
798    Validate input agruments
799    '''
800
801    blob_storage_arguments_provided = blob_storage_target_state is not None or\
802        storage_account is not None or storage_endpoint is not None or\
803        storage_account_access_key is not None or\
804        retention_days is not None
805
806    log_analytics_arguments_provided = log_analytics_target_state is not None or\
807        log_analytics_workspace_resource_id is not None
808
809    event_hub_arguments_provided = event_hub_target_state is not None or\
810        event_hub_authorization_rule_id is not None or\
811        event_hub_name is not None
812
813    if not state and not blob_storage_arguments_provided and\
814            not log_analytics_arguments_provided and not event_hub_arguments_provided:
815        raise CLIError('Either state or blob storage or log analytics or event hub arguments are missing')
816
817    if _is_audit_policy_state_enabled(state) and\
818            blob_storage_target_state is None and log_analytics_target_state is None and event_hub_target_state is None:
819        raise CLIError('One of the following arguments must be enabled:'
820                       ' blob-storage-target-state, log-analytics-target-state, event-hub-target-state')
821
822    if _is_audit_policy_state_disabled(state) and\
823            (blob_storage_arguments_provided or
824             log_analytics_arguments_provided or
825             event_hub_name):
826        raise CLIError('No additional arguments should be provided once state is disabled')
827
828    if (_is_audit_policy_state_none_or_disabled(blob_storage_target_state)) and\
829            (storage_account is not None or storage_endpoint is not None or
830             storage_account_access_key is not None):
831        raise CLIError('Blob storage account arguments cannot be specified'
832                       ' if blob-storage-target-state is not provided or disabled')
833
834    if _is_audit_policy_state_enabled(blob_storage_target_state):
835        if storage_account is not None and storage_endpoint is not None:
836            raise CLIError('storage-account and storage-endpoint cannot be provided at the same time')
837
838        if storage_account is None and storage_endpoint is None:
839            raise CLIError('Either storage-account or storage-endpoint must be provided')
840
841    # Server upper limit
842    max_retention_days = 3285
843
844    if retention_days is not None and\
845            (int(retention_days) <= 0 or int(retention_days) >= max_retention_days):
846        raise CLIError('retention-days must be a positive number greater than zero and lower than {}'
847                       .format(max_retention_days))
848
849    if _is_audit_policy_state_none_or_disabled(log_analytics_target_state) and\
850            log_analytics_workspace_resource_id is not None:
851        raise CLIError('Log analytics workspace resource id cannot be specified'
852                       ' if log-analytics-target-state is not provided or disabled')
853
854    if _is_audit_policy_state_enabled(log_analytics_target_state) and\
855            log_analytics_workspace_resource_id is None:
856        raise CLIError('Log analytics workspace resource id must be specified'
857                       ' if log-analytics-target-state is enabled')
858
859    if _is_audit_policy_state_none_or_disabled(event_hub_target_state) and\
860            (event_hub_authorization_rule_id is not None or event_hub_name is not None):
861        raise CLIError('Event hub arguments cannot be specified if event-hub-target-state is not provided or disabled')
862
863    if _is_audit_policy_state_enabled(event_hub_target_state) and event_hub_authorization_rule_id is None:
864        raise CLIError('event-hub-authorization-rule-id must be specified if event-hub-target-state is enabled')
865
866
867def _get_diagnostic_settings_url(
868        cmd,
869        resource_group_name,
870        workspace_name,
871        sql_pool_name=None):
872
873    from azure.cli.core.commands.client_factory import get_subscription_id
874
875    diag_settings = '/subscriptions/{}/resourceGroups/{}/providers/Microsoft.Synapse/workspaces/{}'.format(
876        get_subscription_id(cmd.cli_ctx),
877        resource_group_name, workspace_name)
878
879    if sql_pool_name is not None:
880        diag_settings = diag_settings + '/sqlpools/{}'.format(sql_pool_name)
881
882    return diag_settings
883
884
885def _get_diagnostic_settings(
886        cmd,
887        resource_group_name,
888        workspace_name,
889        sql_pool_name=None):
890    '''
891    Common code to get server or database diagnostic settings
892    '''
893
894    diagnostic_settings_url = _get_diagnostic_settings_url(
895        cmd=cmd, resource_group_name=resource_group_name,
896        workspace_name=workspace_name, sql_pool_name=sql_pool_name)
897    azure_monitor_client = cf_monitor(cmd.cli_ctx)
898
899    return azure_monitor_client.diagnostic_settings.list(diagnostic_settings_url)
900
901
902def workspace_audit_policy_show(
903        cmd,
904        client,
905        workspace_name,
906        resource_group_name,
907        blob_auditing_policy_name=None):
908    '''
909    Show workspace audit policy
910    '''
911
912    return _audit_policy_show(
913        cmd=cmd,
914        client=client,
915        resource_group_name=resource_group_name,
916        workspace_name=workspace_name,
917        blob_auditing_policy_name=blob_auditing_policy_name,
918        category_name='SQLSecurityAuditEvents')
919
920
921def sqlpool_audit_policy_show(
922        cmd,
923        client,
924        workspace_name,
925        resource_group_name,
926        sql_pool_name,
927        blob_auditing_policy_name=None):
928    '''
929    Show sql pool audit policy
930    '''
931
932    return _audit_policy_show(
933        cmd=cmd,
934        client=client,
935        resource_group_name=resource_group_name,
936        workspace_name=workspace_name,
937        sql_pool_name=sql_pool_name,
938        blob_auditing_policy_name=blob_auditing_policy_name,
939        category_name='SQLSecurityAuditEvents')
940
941
942def _audit_policy_show(
943        cmd,
944        client,
945        resource_group_name,
946        workspace_name,
947        sql_pool_name=None,
948        category_name=None,
949        blob_auditing_policy_name=None):
950    '''
951    Common code to get workspace or sqlpool audit policy including diagnostic settings
952    '''
953
954    # Request audit policy
955    if sql_pool_name is None:
956        audit_policy = client.get(
957            resource_group_name=resource_group_name,
958            workspace_name=workspace_name,
959            blob_auditing_policy_name=blob_auditing_policy_name)
960    else:
961        audit_policy = client.get(
962            resource_group_name=resource_group_name,
963            workspace_name=workspace_name,
964            sql_pool_name=sql_pool_name)
965
966    audit_policy.blob_storage_target_state = BlobAuditingPolicyState.disabled
967    audit_policy.event_hub_target_state = BlobAuditingPolicyState.disabled
968    audit_policy.log_analytics_target_state = BlobAuditingPolicyState.disabled
969
970    # If audit policy's state is disabled there is nothing to do
971    if _is_audit_policy_state_disabled(audit_policy.state):
972        return audit_policy
973
974    if not audit_policy.storage_endpoint:
975        audit_policy.blob_storage_target_state = BlobAuditingPolicyState.disabled
976    else:
977        audit_policy.blob_storage_target_state = BlobAuditingPolicyState.enabled
978
979    # If 'is_azure_monitor_target_enabled' is false there is no reason to request diagnostic settings
980    if not audit_policy.is_azure_monitor_target_enabled:
981        return audit_policy
982
983    # Request diagnostic settings
984    diagnostic_settings = _get_diagnostic_settings(
985        cmd=cmd, resource_group_name=resource_group_name,
986        workspace_name=workspace_name, sql_pool_name=sql_pool_name)
987
988    # Sort received diagnostic settings by name and get first element to ensure consistency between command executions
989    diagnostic_settings.value.sort(key=lambda d: d.name)
990    audit_diagnostic_setting = _fetch_first_audit_diagnostic_setting(diagnostic_settings.value, category_name)
991
992    # Initialize azure monitor properties
993    if audit_diagnostic_setting is not None:
994        if audit_diagnostic_setting.workspace_id is not None:
995            audit_policy.log_analytics_target_state = BlobAuditingPolicyState.enabled
996            audit_policy.log_analytics_workspace_resource_id = audit_diagnostic_setting.workspace_id
997
998        if audit_diagnostic_setting.event_hub_authorization_rule_id is not None:
999            audit_policy.event_hub_target_state = BlobAuditingPolicyState.enabled
1000            audit_policy.event_hub_authorization_rule_id = audit_diagnostic_setting.event_hub_authorization_rule_id
1001            audit_policy.event_hub_name = audit_diagnostic_setting.event_hub_name
1002
1003    return audit_policy
1004