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
feat(azure): New azure monitor check monitor_ensure_diagnostic_setting_appropriate
#3421
feat(azure): New azure monitor check monitor_ensure_diagnostic_setting_appropriate
#3421
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #3421 +/- ##
==========================================
- Coverage 85.73% 85.72% -0.02%
==========================================
Files 634 637 +3
Lines 20007 20069 +62
==========================================
+ Hits 17154 17204 +50
- Misses 2853 2865 +12 ☔ View full report in Codecov by Sentry. |
"RelatedUrl": "https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/diagnostic-settings", | ||
"Remediation": { | ||
"Code": { | ||
"CLI": "az monitor diagnostic-settings subscription list --subscription <subscription ID>", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"CLI": "az monitor diagnostic-settings subscription list --subscription <subscription ID>", | |
"CLI": "", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you put the one in TrendMicro, please?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great one @Hugo966 !!
I left you some comments to clarify some parts of the code. Thanks!
}, | ||
"Recommendation": { | ||
"Text": "1. Go to Azure Monitor 2. Click Activity log 3. Click on Export Activity Logs 4. Select the Subscription from the drop down menu 5. Click on Add diagnostic setting 6. Enter a name for your new Diagnostic Setting 7. Check the following categories: Administrative, Alert, Policy, and Security 8. Choose the destination details according to your organization's needs.", | ||
"Url": "" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You have to always fill this value here. Use a link from the official documentation.
# self.activity_logs = self.__get_activity_logs__() | ||
# self.activity_log_alerts = self.__get_activity_log_alerts__() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this is not needed, please remove it from the service.
# def __get_activity_logs__(self): | ||
# logger.info("Monitor - Getting activity logs...") | ||
# activity_logs = {} | ||
# for subscription, client in self.clients.items(): | ||
# try: | ||
# activity_logs.update({subscription: []}) | ||
# start_time = datetime.datetime.now() - datetime.timedelta(days=90) | ||
# end_time = datetime.datetime.now() | ||
# logs = client.activity_logs.list( | ||
# filter=f"eventTimestamp ge {start_time.isoformat()} and eventTimestamp le {end_time.isoformat()}" | ||
# ) | ||
# for log in logs: | ||
# activity_logs[subscription].append( | ||
# ActivityLog( | ||
# id=log.id, | ||
# resource_group=log.resource_group_name, | ||
# resource_id=log.resource_id, | ||
# operation_name=log.operation_name, | ||
# status=log.status, | ||
# event_timestamp=log.event_timestamp, | ||
# caller=log.caller, | ||
# correlation_id=log.correlation_id, | ||
# category=log.category, | ||
# level=log.level, | ||
# event_data_id=log.event_data_id, | ||
# sub_status=log.sub_status, | ||
# ) | ||
# ) | ||
# except Exception as error: | ||
# logger.error( | ||
# f"Subscription name: {subscription} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" | ||
# ) | ||
# #print(activity_logs) | ||
# print("HOLA") | ||
# return activity_logs | ||
|
||
# def __get_activity_log_alerts__(self): | ||
# logger.info("Monitor - Getting activity log alerts...") | ||
# activity_log_alerts = {} | ||
# for subscription, client in self.clients.items(): | ||
# try: | ||
# activity_log_alerts.update({subscription: []}) | ||
# alerts = client.activity_log_alerts.list_by_subscription_id() | ||
# for alert in alerts: | ||
# print(alert.condition.all_of[0].equals[0].value) | ||
# activity_log_alerts[subscription].append( | ||
# ActivityLogAlert( | ||
# id=alert.id, | ||
# name=alert.name, | ||
# description=alert.description, | ||
# enabled=alert.enabled, | ||
# scopes=alert.scopes, | ||
# condition=alert.condition, | ||
# actions=alert.actions, | ||
# last_updated_time=alert.last_updated_time, | ||
# provisioning_state=alert.provisioning_state, | ||
# ) | ||
# ) | ||
# except Exception as error: | ||
# logger.error( | ||
# f"Subscription name: {subscription} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" | ||
# ) | ||
# return activity_log_alerts | ||
|
||
|
||
# @dataclass | ||
# class ActivityLog: | ||
# id: str | ||
# resource_group: str | ||
# resource_id: str | ||
# operation_name: str | ||
# status: str | ||
# event_timestamp: str | ||
# caller: str | ||
# correlation_id: str | ||
# category: str | ||
# level: str | ||
# event_data_id: str | ||
# sub_status: str |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The same again.
# @dataclass | ||
# class ActivityLogAlert: | ||
# id: str | ||
# name: str | ||
# description: str | ||
# enabled: bool | ||
# scopes: list | ||
# condition: dict | ||
# actions: list | ||
# last_updated_time: str | ||
# provisioning_state: str |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# @dataclass | |
# class ActivityLogAlert: | |
# id: str | |
# name: str | |
# description: str | |
# enabled: bool | |
# scopes: list | |
# condition: dict | |
# actions: list | |
# last_updated_time: str | |
# provisioning_state: str |
# sub_status: str | ||
|
||
|
||
@dataclass |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are you not using Pydantic here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because of the types, logs is a list that consists of 2 strings, a boolean and a RetentionPolicy that consists of an integer and a boolean, when I structure this using Pydantic somehow Python doesn't get the data correctly and execution fails.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you set the type for the logs
? Like list[WhateverIsNamedInAzureMonitor]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is what I tried, but Python wasn't getting the types correctly. This is what I did:
class RetentionPolicy(BaseModel):
enabled: bool
days: int
class LogSettings(BaseModel):
category: str
category_group: str
enabled: bool
retention_policy: RetentionPolicy
class DiagnosticSetting(BaseModel):
id: str
logs: List[LogSettings]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But you only have the DiagnosticSetting
and logs: list
, is this right? I mean that the LogSettings
import is not present anymore.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's correct, that was the old version trying to use Pydantic, the new one is:
from azure.mgmt.monitor.models import LogSettings
from pydantic import BaseModel
class DiagnosticSetting(BaseModel):
id: str
logs: LogSettings
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's good, push that change then!
DiagnosticSetting( | ||
id=setting.id, | ||
name=setting.name, | ||
type=setting.type, | ||
event_hub_authorization_rule_id=setting.event_hub_authorization_rule_id, | ||
event_hub_name=setting.event_hub_name, | ||
metrics=setting.metrics, | ||
logs=setting.logs, | ||
workspace_id=setting.workspace_id, | ||
storage_account_id=setting.storage_account_id, | ||
service_bus_rule_id=setting.service_bus_rule_id, | ||
marketplace_partner_id=setting.marketplace_partner_id, | ||
log_analytics_destination_type=setting.log_analytics_destination_type, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Are you sure that all of these values are always present?
- Do you need all of these values for the check?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These values can be None, but I'll let only the only ones that I need for the check.
I though I should include all of them even if I don't need them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Always try to only include the attributes you need for the checks you are coding, keep it simple.
diagnostic_setting.logs[0].enabled | ||
and diagnostic_setting.logs[1].enabled | ||
and diagnostic_setting.logs[3].enabled | ||
and diagnostic_setting.logs[5].enabled |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are you checking items 0, 1, 3 and 5 here? What is the reason behind?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Im iterating through the logs and checking if the ones asked to be enabled ('Administrative','Security','Alert' and 'Policy') are enabled. Those numbers correspond to the order that those categories follow in the Azure Portal.
However, Im going to change it and upgrade it by checking also the category name so that way is guaranteed that even if more categories are set in the future, the code will still work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perfect, thanks!
mock.MagicMock(enabled=True), | ||
mock.MagicMock(enabled=True), | ||
mock.MagicMock(enabled=True), | ||
mock.MagicMock(enabled=True), | ||
mock.MagicMock(enabled=False), | ||
mock.MagicMock(enabled=True), | ||
mock.MagicMock(enabled=False), | ||
mock.MagicMock(enabled=False), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the meaning of this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Creating a valid object for testing my check, with the new code, its also creates the category for each one.
"Categories": [], | ||
"DependsOn": [], | ||
"RelatedTo": [], | ||
"Notes": "" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Notes": "" | |
"Notes": "When the diagnostic setting is created using Azure Portal, by default no categories are selected." |
@@ -0,0 +1,30 @@ | |||
{ | |||
"Provider": "azure", | |||
"CheckID": "monitor_ensure_diagnostic_setting_appropiate", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"CheckID": "monitor_ensure_diagnostic_setting_appropiate", | |
"CheckID": "monitor_diagnostic_setting_with_appropriate_categories", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there is a typo, it is appropriate
not appropiate
monitor_ensure_diagnostic_setting_appropiate
monitor_ensure_diagnostic_setting_appropriate
"Categories": [], | ||
"DependsOn": [], | ||
"RelatedTo": [], | ||
"Notes": "" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Notes": "" | |
"Notes": "When the diagnostic setting is created using Azure Portal, by default no categories are selected." |
..._appropriate_categories/monitor_diagnostic_setting_with_appropriate_categories.metadata.json
Outdated
Show resolved
Hide resolved
...etting_with_appropriate_categories/monitor_diagnostic_setting_with_appropriate_categories.py
Show resolved
Hide resolved
# sub_status: str | ||
|
||
|
||
@dataclass |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But you only have the DiagnosticSetting
and logs: list
, is this right? I mean that the LogSettings
import is not present anymore.
…tting_with_appropriate_categories/monitor_diagnostic_setting_with_appropriate_categories.metadata.json Co-authored-by: Pepe Fagoaga <pepe@verica.io>
…r into azure_monitor_5.1.2_check Merge necessary
report.status = "PASS" | ||
report.status_extended = f"There is at least one diagnostic setting capturing appropiate categories in subscription {subscription_name}." | ||
break | ||
if report.status == "PASS": |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you change this and use:
administrative_enabled
and security_enabled
and service_health_enabled
and alert_enabled
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand what do you want me to change
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean to change report.status == "PASS":
for
if administrative_enabled
and security_enabled
and service_health_enabled
and alert_enabled:
More readable for me, but not so important.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now I get it, done
assert len(monitor.diagnostics_settings) == 1 | ||
assert monitor.diagnostics_settings[AZURE_SUBSCRIPTION][0].id == "id" | ||
assert ( | ||
monitor.diagnostics_settings[AZURE_SUBSCRIPTION][0].logs[0].enabled is True |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't need the is True
when doing asserts
monitor.diagnostics_settings[AZURE_SUBSCRIPTION][0].logs[0].enabled is True | |
monitor.diagnostics_settings[AZURE_SUBSCRIPTION][0].logs[0].enabled |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
monitor.diagnostics_settings[AZURE_SUBSCRIPTION][0].logs[1].category | ||
== "Security" | ||
) | ||
assert ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you also assert here the enable
status? And for the rest not included.
assert (
monitor.diagnostics_settings[AZURE_SUBSCRIPTION][0].logs[2].enabled is True
)
Also iterate through the logs like you do in the check not to break the test if the indexes changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, Im not checking enabled for the categories that are not include in the check because they can be enabled or disabled, both are a PASS for the check in that category.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah ok, good to know, thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work @Hugo966 👏
Thanks for the efforts to address all the suggestions!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good job on your first check @Hugo966, way to go 🚀
Context
New azure monitor check
monitor_ensure_diagnostic_setting_appropriate
Description
New monitor check ensuring diagnostics setting are set correctly
License
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.