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

feat(azure): New azure monitor check monitor_ensure_diagnostic_setting_appropriate #3421

Merged
merged 19 commits into from Feb 26, 2024

Conversation

Hugo966
Copy link
Contributor

@Hugo966 Hugo966 commented Feb 20, 2024

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.

@Hugo966 Hugo966 requested a review from a team as a code owner February 20, 2024 13:42
@github-actions github-actions bot added the provider/azure Issues/PRs related with the Azure provider label Feb 20, 2024
Copy link

codecov bot commented Feb 20, 2024

Codecov Report

Attention: Patch coverage is 88.88889% with 7 lines in your changes are missing coverage. Please review.

Project coverage is 85.72%. Comparing base (587b8af) to head (6af2c37).

Files Patch % Lines
...roviders/azure/services/monitor/monitor_service.py 73.07% 7 Missing ⚠️
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.
📢 Have feedback on the report? Share it here.

"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>",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"CLI": "az monitor diagnostic-settings subscription list --subscription <subscription ID>",
"CLI": "",

Copy link
Member

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?

Copy link
Member

@jfagoagas jfagoagas left a 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": ""
Copy link
Member

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.

Comment on lines 13 to 14
# self.activity_logs = self.__get_activity_logs__()
# self.activity_log_alerts = self.__get_activity_log_alerts__()
Copy link
Member

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.

Comment on lines 50 to 128
# 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same again.

Comment on lines 147 to 157
# @dataclass
# class ActivityLogAlert:
# id: str
# name: str
# description: str
# enabled: bool
# scopes: list
# condition: dict
# actions: list
# last_updated_time: str
# provisioning_state: str
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# @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
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Member

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]

Copy link
Contributor Author

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]

Copy link
Member

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.

Copy link
Contributor Author

@Hugo966 Hugo966 Feb 22, 2024

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

Copy link
Member

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!

Comment on lines 28 to 40
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,
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Member

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.

Comment on lines 21 to 24
diagnostic_setting.logs[0].enabled
and diagnostic_setting.logs[1].enabled
and diagnostic_setting.logs[3].enabled
and diagnostic_setting.logs[5].enabled
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perfect, thanks!

Comment on lines 25 to 32
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),
Copy link
Member

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?

Copy link
Contributor Author

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": ""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"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",
Copy link
Member

@sergargar sergargar Feb 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"CheckID": "monitor_ensure_diagnostic_setting_appropiate",
"CheckID": "monitor_diagnostic_setting_with_appropriate_categories",

Copy link
Member

@toniblyx toniblyx left a 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

@sergargar sergargar changed the title feat(azure): New azure monitor check monitor_ensure_diagnostic_setting_appropiate feat(azure): New azure monitor check monitor_ensure_diagnostic_setting_appropriate Feb 22, 2024
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"Notes": ""
"Notes": "When the diagnostic setting is created using Azure Portal, by default no categories are selected."

@jfagoagas jfagoagas self-requested a review February 22, 2024 10:49
# sub_status: str


@dataclass
Copy link
Member

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.

Hugo966 and others added 4 commits February 22, 2024 12:08
…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
@jfagoagas jfagoagas self-requested a review February 22, 2024 13:22
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":
Copy link
Member

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

Copy link
Contributor Author

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

Copy link
Member

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.

Copy link
Contributor Author

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
Copy link
Member

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

Suggested change
monitor.diagnostics_settings[AZURE_SUBSCRIPTION][0].logs[0].enabled is True
monitor.diagnostics_settings[AZURE_SUBSCRIPTION][0].logs[0].enabled

Copy link
Contributor Author

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 (
Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Member

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!

jfagoagas
jfagoagas previously approved these changes Feb 22, 2024
Copy link
Member

@jfagoagas jfagoagas left a 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!

sergargar
sergargar previously approved these changes Feb 23, 2024
Copy link
Member

@sergargar sergargar left a 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 🚀

sergargar
sergargar previously approved these changes Feb 26, 2024
@sergargar sergargar self-requested a review February 26, 2024 13:29
@sergargar sergargar merged commit 963861d into prowler-cloud:master Feb 26, 2024
10 of 11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
provider/azure Issues/PRs related with the Azure provider
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants