From 1bfb0cdc44750c3f72134e66d5fd0d4a457b15b7 Mon Sep 17 00:00:00 2001 From: Khushboo <46913995+khushboo9024@users.noreply.github.com> Date: Mon, 16 Dec 2024 13:30:57 +0530 Subject: [PATCH 1/3] Add new pipelines (#1) Co-authored-by: Priyanka Chatterjee Co-authored-by: misraved --- README.md | 195 ++++++++- locals.fp | 42 ++ mod.fp | 11 +- pipelines/appservice/appservice.fp | 5 + ...register_with_active_directory_disabled.fp | 319 ++++++++++++++ ...e_web_apps_with_authentication_disabled.fp | 320 ++++++++++++++ ...ce_web_apps_with_ftp_deployment_enabled.fp | 321 ++++++++++++++ ..._web_apps_with_remote_debugging_enabled.fp | 320 ++++++++++++++ ...pservice_web_apps_without_https_enabled.fp | 320 ++++++++++++++ ...ce_web_apps_without_latest_http_version.fp | 320 ++++++++++++++ ...ice_web_apps_without_latest_php_version.fp | 364 ++++++++++++++++ ..._web_apps_without_latest_python_version.fp | 394 ++++++++++++++++++ ...ice_web_apps_without_latest_tls_version.fp | 320 ++++++++++++++ pipelines/compute/compute.fp | 5 + ...e_attached_disks_not_encrypted_with_cmk.fp | 132 ++++++ ...sks_with_data_access_auth_mode_disabled.fp | 319 ++++++++++++++ ...ompute_disks_with_public_access_enabled.fp | 135 ++++++ ...unattached_disks_not_encrypted_with_cmk.fp | 138 ++++++ .../compute_vms_without_managed_disk.fp | 130 ++++++ pipelines/cosmosdb/cosmosdb.fp | 5 + ...ts_with_virtual_network_filter_disabled.fp | 130 ++++++ .../cosmosdb_accounts_without_private_link.fp | 140 +++++++ pipelines/iam/iam.fp | 5 + ...icies_allowing_to_create_security_group.fp | 138 ++++++ ...licies_allowing_to_register_application.fp | 137 ++++++ ...am_conditional_access_with_mfa_disabled.fp | 136 ++++++ ...ss_with_mfa_disabled_for_administrators.fp | 141 +++++++ ...m_subscriptions_with_custom_owner_roles.fp | 312 ++++++++++++++ pipelines/iam/tenants_with_guest_users.fp | 141 +++++++ ...more_than_five_iam_global_administrator.fp | 138 ++++++ pipelines/keyvault/keyvault.fp | 5 + .../keyvault_vaults_with_logging_disabled.fp | 148 +++++++ ...t_vaults_with_purge_protection_disabled.fp | 319 ++++++++++++++ .../keyvault_vaults_with_rbac_disabled.fp | 319 ++++++++++++++ .../keyvault_vaults_without_private_link.fp | 133 ++++++ ...t_with_non_rbac_keys_expiration_not_set.fp | 143 +++++++ ...ith_non_rbac_secrets_expiration_not_set.fp | 143 +++++++ ...vault_with_rbac_keys_expiration_not_set.fp | 144 +++++++ ...lt_with_rbac_secrets_expiration_not_set.fp | 144 +++++++ pipelines/monitor/monitor.fp | 5 + ...ts_activity_logs_not_encrypted_with_cmk.fp | 137 ++++++ ...ing_without_capturing_proper_categories.fp | 162 +++++++ ..._log_alert_for_create_policy_assignment.fp | 160 +++++++ ...ctivity_log_alert_for_create_update_nsg.fp | 171 ++++++++ ...ert_for_create_update_security_solution.fp | 182 ++++++++ ...create_update_sql_servers_firewall_rule.fp | 170 ++++++++ ...thout_activity_log_alert_for_delete_nsg.fp | 169 ++++++++ ..._log_alert_for_delete_policy_assignment.fp | 160 +++++++ ..._log_alert_for_delete_public_ip_address.fp | 169 ++++++++ ..._log_alert_for_delete_security_solution.fp | 169 ++++++++ ...rt_for_delete_sql_servers_firewall_rule.fp | 169 ++++++++ ..._log_alert_for_update_public_ip_address.fp | 170 ++++++++ ..._without_application_insight_configured.fp | 138 ++++++ pipelines/mysql/mysql.fp | 5 + ...lexible_servers_with_audit_log_disabled.fp | 324 ++++++++++++++ ...ith_audit_log_events_connection_not_set.fp | 324 ++++++++++++++ ...ysql_flexible_servers_with_ssl_disabled.fp | 324 ++++++++++++++ ...ql_flexible_servers_without_min_tls_1_2.fp | 323 ++++++++++++++ pipelines/network/network.fp | 5 + .../network/network_lbs_with_basic_sku.fp | 132 ++++++ .../network_public_ips_with_basic_sku.fp | 132 ++++++ ...y_groups_allowing_inbound_to_https_port.fp | 372 +++++++++++++++++ ...ity_groups_allowing_inbound_to_rdp_port.fp | 372 +++++++++++++++++ ...ity_groups_allowing_inbound_to_ssh_port.fp | 370 ++++++++++++++++ ...ity_groups_allowing_inbound_to_udp_port.fp | 378 +++++++++++++++++ ...with_retention_period_less_than_90_days.fp | 133 ++++++ .../network_watcher_disabled_in_regions.fp | 139 ++++++ ...scriptions_without_network_bastion_host.fp | 144 +++++++ pipelines/postgresql/postgresql.fp | 5 + ...ntion_days_less_than_or_equal_to_3_days.fp | 352 ++++++++++++++++ ...ers_with_connection_throttling_disabled.fp | 324 ++++++++++++++ ...e_servers_with_log_checkpoints_disabled.fp | 322 ++++++++++++++ ...esql_flexible_servers_with_ssl_disabled.fp | 332 +++++++++++++++ ...ntion_days_less_than_or_equal_to_3_days.fp | 354 ++++++++++++++++ ..._allow_access_to_azure_services_enabled.fp | 332 +++++++++++++++ ...ers_with_connection_throttling_disabled.fp | 326 +++++++++++++++ ...with_infrastructure_encryption_disabled.fp | 132 ++++++ ...l_servers_with_log_checkpoints_disabled.fp | 324 ++++++++++++++ ...l_servers_with_log_connections_disabled.fp | 325 +++++++++++++++ ...ervers_with_log_disconnections_disabled.fp | 324 ++++++++++++++ ...esql_servers_with_log_duration_disabled.fp | 324 ++++++++++++++ .../postgresql_servers_with_ssl_disabled.fp | 323 ++++++++++++++ pipelines/redis/redis.fp | 5 + .../redis/redis_caches_with_basic_sku.fp | 132 ++++++ ...enter_settings_without_mcas_integration.fp | 132 ++++++ ...nter_settings_without_wdatp_integration.fp | 132 ++++++ ..._provisioning_monitoring_agent_disabled.fp | 128 ++++++ ...azure_defender_for_app_service_disabled.fp | 313 ++++++++++++++ ...h_azure_defender_for_container_disabled.fp | 314 ++++++++++++++ ...efender_for_container_registry_disabled.fp | 313 ++++++++++++++ ...th_azure_defender_for_cosmosdb_disabled.fp | 313 ++++++++++++++ ...rs_with_azure_defender_for_dns_disabled.fp | 314 ++++++++++++++ ...th_azure_defender_for_keyvault_disabled.fp | 314 ++++++++++++++ ..._for_open_source_relational_db_disabled.fp | 315 ++++++++++++++ ..._defender_for_resource_manager_disabled.fp | 313 ++++++++++++++ ...with_azure_defender_for_server_disabled.fp | 314 ++++++++++++++ ...with_azure_defender_for_sql_db_disabled.fp | 315 ++++++++++++++ ...ure_defender_for_sql_server_vm_disabled.fp | 315 ++++++++++++++ ...ith_azure_defender_for_storage_disabled.fp | 315 ++++++++++++++ ..._with_security_alerts_to_owner_disabled.fp | 139 ++++++ ...ers_without_additional_email_configured.fp | 141 +++++++ ...enters_without_notify_alerts_configured.fp | 139 ++++++ pipelines/securitycenter/securitycentre.fp | 5 + pipelines/sql/sql.fp | 5 + ...ql_databases_with_public_access_enabled.fp | 334 +++++++++++++++ ...th_transparent_data_encryption_disabled.fp | 329 +++++++++++++++ ...rs_tde_protector_not_encrypted_with_cmk.fp | 132 ++++++ .../sql/sql_servers_with_auditing_disabled.fp | 132 ++++++ ...ting_retention_period_less_than_90_days.fp | 132 ++++++ ...vers_with_public_network_access_enabled.fp | 319 ++++++++++++++ ...thout_active_directory_admin_configured.fp | 131 ++++++ pipelines/storage/storage.fp | 5 + ...ccounts_with_blob_public_access_enabled.fp | 322 ++++++++++++++ ...unts_with_blob_service_logging_disabled.fp | 338 +++++++++++++++ ...accounts_with_blob_soft_delete_disabled.fp | 325 +++++++++++++++ ...ith_default_network_access_rule_allowed.fp | 329 +++++++++++++++ ...h_encryption_at_rest_using_cmk_disabled.fp | 135 ++++++ ...with_infrastructure_encryption_disabled.fp | 132 ++++++ .../storage_accounts_with_no_min_tls_1_2.fp | 322 ++++++++++++++ ...age_accounts_with_public_access_enabled.fp | 319 ++++++++++++++ ...nts_with_queue_service_logging_disabled.fp | 341 +++++++++++++++ ..._with_secure_transfer_required_disabled.fp | 324 ++++++++++++++ ...nts_with_table_service_logging_disabled.fp | 340 +++++++++++++++ ...ith_trusted_microsoft_services_disabled.fp | 321 ++++++++++++++ .../storage_accounts_without_private_link.fp | 141 +++++++ variables.fp | 37 ++ 126 files changed, 26698 insertions(+), 6 deletions(-) create mode 100644 locals.fp create mode 100644 pipelines/appservice/appservice.fp create mode 100644 pipelines/appservice/appservice_web_apps_register_with_active_directory_disabled.fp create mode 100644 pipelines/appservice/appservice_web_apps_with_authentication_disabled.fp create mode 100644 pipelines/appservice/appservice_web_apps_with_ftp_deployment_enabled.fp create mode 100644 pipelines/appservice/appservice_web_apps_with_remote_debugging_enabled.fp create mode 100644 pipelines/appservice/appservice_web_apps_without_https_enabled.fp create mode 100644 pipelines/appservice/appservice_web_apps_without_latest_http_version.fp create mode 100644 pipelines/appservice/appservice_web_apps_without_latest_php_version.fp create mode 100644 pipelines/appservice/appservice_web_apps_without_latest_python_version.fp create mode 100644 pipelines/appservice/appservice_web_apps_without_latest_tls_version.fp create mode 100644 pipelines/compute/compute.fp create mode 100644 pipelines/compute/compute_attached_disks_not_encrypted_with_cmk.fp create mode 100644 pipelines/compute/compute_disks_with_data_access_auth_mode_disabled.fp create mode 100644 pipelines/compute/compute_disks_with_public_access_enabled.fp create mode 100644 pipelines/compute/compute_unattached_disks_not_encrypted_with_cmk.fp create mode 100644 pipelines/compute/compute_vms_without_managed_disk.fp create mode 100644 pipelines/cosmosdb/cosmosdb.fp create mode 100644 pipelines/cosmosdb/cosmosdb_accounts_with_virtual_network_filter_disabled.fp create mode 100644 pipelines/cosmosdb/cosmosdb_accounts_without_private_link.fp create mode 100644 pipelines/iam/iam.fp create mode 100644 pipelines/iam/iam_authorization_policies_allowing_to_create_security_group.fp create mode 100644 pipelines/iam/iam_authorization_policies_allowing_to_register_application.fp create mode 100644 pipelines/iam/iam_conditional_access_with_mfa_disabled.fp create mode 100644 pipelines/iam/iam_conditional_access_with_mfa_disabled_for_administrators.fp create mode 100644 pipelines/iam/iam_subscriptions_with_custom_owner_roles.fp create mode 100644 pipelines/iam/tenants_with_guest_users.fp create mode 100644 pipelines/iam/tenants_with_more_than_five_iam_global_administrator.fp create mode 100644 pipelines/keyvault/keyvault.fp create mode 100644 pipelines/keyvault/keyvault_vaults_with_logging_disabled.fp create mode 100644 pipelines/keyvault/keyvault_vaults_with_purge_protection_disabled.fp create mode 100644 pipelines/keyvault/keyvault_vaults_with_rbac_disabled.fp create mode 100644 pipelines/keyvault/keyvault_vaults_without_private_link.fp create mode 100644 pipelines/keyvault/keyvault_with_non_rbac_keys_expiration_not_set.fp create mode 100644 pipelines/keyvault/keyvault_with_non_rbac_secrets_expiration_not_set.fp create mode 100644 pipelines/keyvault/keyvault_with_rbac_keys_expiration_not_set.fp create mode 100644 pipelines/keyvault/keyvault_with_rbac_secrets_expiration_not_set.fp create mode 100644 pipelines/monitor/monitor.fp create mode 100644 pipelines/monitor/monitor_storage_containers_insights_activity_logs_not_encrypted_with_cmk.fp create mode 100644 pipelines/monitor/subscriptions_diagnostic_setting_without_capturing_proper_categories.fp create mode 100644 pipelines/monitor/subscriptions_without_activity_log_alert_for_create_policy_assignment.fp create mode 100644 pipelines/monitor/subscriptions_without_activity_log_alert_for_create_update_nsg.fp create mode 100644 pipelines/monitor/subscriptions_without_activity_log_alert_for_create_update_security_solution.fp create mode 100644 pipelines/monitor/subscriptions_without_activity_log_alert_for_create_update_sql_servers_firewall_rule.fp create mode 100644 pipelines/monitor/subscriptions_without_activity_log_alert_for_delete_nsg.fp create mode 100644 pipelines/monitor/subscriptions_without_activity_log_alert_for_delete_policy_assignment.fp create mode 100644 pipelines/monitor/subscriptions_without_activity_log_alert_for_delete_public_ip_address.fp create mode 100644 pipelines/monitor/subscriptions_without_activity_log_alert_for_delete_security_solution.fp create mode 100644 pipelines/monitor/subscriptions_without_activity_log_alert_for_delete_sql_servers_firewall_rule.fp create mode 100644 pipelines/monitor/subscriptions_without_activity_log_alert_for_update_public_ip_address.fp create mode 100644 pipelines/monitor/subscriptions_without_application_insight_configured.fp create mode 100644 pipelines/mysql/mysql.fp create mode 100644 pipelines/mysql/mysql_flexible_servers_with_audit_log_disabled.fp create mode 100644 pipelines/mysql/mysql_flexible_servers_with_audit_log_events_connection_not_set.fp create mode 100644 pipelines/mysql/mysql_flexible_servers_with_ssl_disabled.fp create mode 100644 pipelines/mysql/mysql_flexible_servers_without_min_tls_1_2.fp create mode 100644 pipelines/network/network.fp create mode 100644 pipelines/network/network_lbs_with_basic_sku.fp create mode 100644 pipelines/network/network_public_ips_with_basic_sku.fp create mode 100644 pipelines/network/network_security_groups_allowing_inbound_to_https_port.fp create mode 100644 pipelines/network/network_security_groups_allowing_inbound_to_rdp_port.fp create mode 100644 pipelines/network/network_security_groups_allowing_inbound_to_ssh_port.fp create mode 100644 pipelines/network/network_security_groups_allowing_inbound_to_udp_port.fp create mode 100644 pipelines/network/network_security_groups_flow_log_with_retention_period_less_than_90_days.fp create mode 100644 pipelines/network/network_watcher_disabled_in_regions.fp create mode 100644 pipelines/network/subscriptions_without_network_bastion_host.fp create mode 100644 pipelines/postgresql/postgresql.fp create mode 100644 pipelines/postgresql/postgresql_flexible_servers_log_retention_days_less_than_or_equal_to_3_days.fp create mode 100644 pipelines/postgresql/postgresql_flexible_servers_with_connection_throttling_disabled.fp create mode 100644 pipelines/postgresql/postgresql_flexible_servers_with_log_checkpoints_disabled.fp create mode 100644 pipelines/postgresql/postgresql_flexible_servers_with_ssl_disabled.fp create mode 100644 pipelines/postgresql/postgresql_servers_log_retention_days_less_than_or_equal_to_3_days.fp create mode 100644 pipelines/postgresql/postgresql_servers_with_allow_access_to_azure_services_enabled.fp create mode 100644 pipelines/postgresql/postgresql_servers_with_connection_throttling_disabled.fp create mode 100644 pipelines/postgresql/postgresql_servers_with_infrastructure_encryption_disabled.fp create mode 100644 pipelines/postgresql/postgresql_servers_with_log_checkpoints_disabled.fp create mode 100644 pipelines/postgresql/postgresql_servers_with_log_connections_disabled.fp create mode 100644 pipelines/postgresql/postgresql_servers_with_log_disconnections_disabled.fp create mode 100644 pipelines/postgresql/postgresql_servers_with_log_duration_disabled.fp create mode 100644 pipelines/postgresql/postgresql_servers_with_ssl_disabled.fp create mode 100644 pipelines/redis/redis.fp create mode 100644 pipelines/redis/redis_caches_with_basic_sku.fp create mode 100644 pipelines/securitycenter/securitycenter_settings_without_mcas_integration.fp create mode 100644 pipelines/securitycenter/securitycenter_settings_without_wdatp_integration.fp create mode 100644 pipelines/securitycenter/securitycenters_with_automatic_provisioning_monitoring_agent_disabled.fp create mode 100644 pipelines/securitycenter/securitycenters_with_azure_defender_for_app_service_disabled.fp create mode 100644 pipelines/securitycenter/securitycenters_with_azure_defender_for_container_disabled.fp create mode 100644 pipelines/securitycenter/securitycenters_with_azure_defender_for_container_registry_disabled.fp create mode 100644 pipelines/securitycenter/securitycenters_with_azure_defender_for_cosmosdb_disabled.fp create mode 100644 pipelines/securitycenter/securitycenters_with_azure_defender_for_dns_disabled.fp create mode 100644 pipelines/securitycenter/securitycenters_with_azure_defender_for_keyvault_disabled.fp create mode 100644 pipelines/securitycenter/securitycenters_with_azure_defender_for_open_source_relational_db_disabled.fp create mode 100644 pipelines/securitycenter/securitycenters_with_azure_defender_for_resource_manager_disabled.fp create mode 100644 pipelines/securitycenter/securitycenters_with_azure_defender_for_server_disabled.fp create mode 100644 pipelines/securitycenter/securitycenters_with_azure_defender_for_sql_db_disabled.fp create mode 100644 pipelines/securitycenter/securitycenters_with_azure_defender_for_sql_server_vm_disabled.fp create mode 100644 pipelines/securitycenter/securitycenters_with_azure_defender_for_storage_disabled.fp create mode 100644 pipelines/securitycenter/securitycenters_with_security_alerts_to_owner_disabled.fp create mode 100644 pipelines/securitycenter/securitycenters_without_additional_email_configured.fp create mode 100644 pipelines/securitycenter/securitycenters_without_notify_alerts_configured.fp create mode 100644 pipelines/securitycenter/securitycentre.fp create mode 100644 pipelines/sql/sql.fp create mode 100644 pipelines/sql/sql_databases_with_public_access_enabled.fp create mode 100644 pipelines/sql/sql_databases_with_transparent_data_encryption_disabled.fp create mode 100644 pipelines/sql/sql_servers_tde_protector_not_encrypted_with_cmk.fp create mode 100644 pipelines/sql/sql_servers_with_auditing_disabled.fp create mode 100644 pipelines/sql/sql_servers_with_auditing_retention_period_less_than_90_days.fp create mode 100644 pipelines/sql/sql_servers_with_public_network_access_enabled.fp create mode 100644 pipelines/sql/sql_servers_without_active_directory_admin_configured.fp create mode 100644 pipelines/storage/storage.fp create mode 100644 pipelines/storage/storage_accounts_with_blob_public_access_enabled.fp create mode 100644 pipelines/storage/storage_accounts_with_blob_service_logging_disabled.fp create mode 100644 pipelines/storage/storage_accounts_with_blob_soft_delete_disabled.fp create mode 100644 pipelines/storage/storage_accounts_with_default_network_access_rule_allowed.fp create mode 100644 pipelines/storage/storage_accounts_with_encryption_at_rest_using_cmk_disabled.fp create mode 100644 pipelines/storage/storage_accounts_with_infrastructure_encryption_disabled.fp create mode 100644 pipelines/storage/storage_accounts_with_no_min_tls_1_2.fp create mode 100644 pipelines/storage/storage_accounts_with_public_access_enabled.fp create mode 100644 pipelines/storage/storage_accounts_with_queue_service_logging_disabled.fp create mode 100644 pipelines/storage/storage_accounts_with_secure_transfer_required_disabled.fp create mode 100644 pipelines/storage/storage_accounts_with_table_service_logging_disabled.fp create mode 100644 pipelines/storage/storage_accounts_with_trusted_microsoft_services_disabled.fp create mode 100644 pipelines/storage/storage_accounts_without_private_link.fp create mode 100644 variables.fp diff --git a/README.md b/README.md index a531c82..d7e4880 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,193 @@ -# flowpipe-mod-azure-compliance -Flowpipe mod for Azure compliance +# Azure Compliance Mod for Flowpipe + +Pipelines to detect and remediate misconfigurations in Azure resources. + +## Documentation + +- **[Pipelines →](https://hub.flowpipe.io/mods/turbot/azure_compliance/pipelines)** + +## Getting Started + +### Requirements + +Docker daemon must be installed and running. Please see [Install Docker Engine](https://docs.docker.com/engine/install/) for more information. + +### Installation + +Download and install Flowpipe (https://flowpipe.io/downloads) and Steampipe (https://steampipe.io/downloads). Or use Brew: + +```sh +brew install turbot/tap/flowpipe +brew install turbot/tap/steampipe +``` + +Install the Azure plugin with [Steampipe](https://steampipe.io): + +```sh +steampipe plugin install azure +``` + +Steampipe will automatically use your default Azure credentials. Optionally, you can [setup multiple subscriptions](https://hub.steampipe.io/plugins/turbot/azure#multi-subscription-connections) or [customize Azure credentials](https://hub.steampipe.io/plugins/turbot/azure#configuring-azure-credentials). + +Create a `connection_import` resource to import your Steampipe Azure connections: + +```sh +vi ~/.flowpipe/config/azure.fpc +``` + +```hcl +connection_import "azure" { + source = "~/.steampipe/config/azure.spc" + connections = ["*"] +} +``` + +For more information on importing connections, please see [Connection Import](https://flowpipe.io/docs/reference/config-files/connection_import). + +For more information on connections in Flowpipe, please see [Managing Connections](https://flowpipe.io/docs/run/connections). + +Install the mod: + +```sh +mkdir azure-compliance +cd azure-compliance +flowpipe mod install github.com/turbot/flowpipe-mod-azure-compliance +``` + +### Running Detect and Correct Pipelines + +To run your first detection, you'll need to ensure your Steampipe server is up and running: + +```sh +steampipe service start +``` + +To find your desired detection, you can filter the `pipeline list` output: + +```sh +flowpipe pipeline list | grep "detect_and_correct" +``` + +Then run your chosen pipeline: + +```sh +flowpipe pipeline run detect_and_correct_sql_databases_with_public_access_enabled +``` + +This will then run the pipeline and depending on your configured running mode; perform the relevant action(s), there are 3 running modes: +- Wizard +- Notify +- Automatic + +#### Wizard + +This is the `default` running mode, allowing for a hands-on approach to approving changes to resources by prompting for [input](https://flowpipe.io/docs/build/input) for each detected resource. + +Whilst the out of the box default is to run the workflow directly in the terminal. You can use Flowpipe [server](https://flowpipe.io/docs/run/server) and [external integrations](https://flowpipe.io/docs/build/input#create-an-integration) to prompt in `http`, `slack`, `teams`, etc. + +#### Notify + +This mode as the name implies is used purely to report detections via notifications either directly to your terminal when running in client mode or via another configured [notifier](https://flowpipe.io/docs/reference/config-files/notifier) when running in server mode for each detected resource. + +To run in `notify` mode, you will need to set the `approvers` variable to an empty list `[]` and ensure the resource-specific `default_action` variable is set to `notify`, either in your `flowpipe.fpvars` file: + +```hcl +approvers = [] +sql_databases_with_public_access_enabled_default_action = "notify" +``` + +or pass the `approvers` and `default_action` arguments on the command-line. + +```sh +flowpipe pipeline run detect_and_correct_sql_databases_with_public_access_enabled --arg='default_action=notify' --arg='approvers=[]' +``` + +#### Automatic + +This behavior allows for a hands-off approach to remediating resources. + +To run in `automatic` mode, you will need to set the `approvers` variable to an empty list `[]` and the the resource-specific `default_action` variable to one of the available options in your `flowpipe.fpvars` file: + +```hcl +approvers = [] +sql_databases_with_public_access_enabled_default_action = "revoke_firewall_rule" +``` + +or pass the `approvers` and `default_action` argument on the command-line. + +```sh +flowpipe pipeline run detect_and_correct_sql_databases_with_public_access_enabled --arg='approvers=[] --arg='default_action=revoke_firewall_rule' +``` + +To further enhance this approach, you can enable the pipelines corresponding [query trigger](#running-query-triggers) to run completely hands-off. + +### Running Query Triggers + +> Note: Query triggers require Flowpipe running in [server](https://flowpipe.io/docs/run/server) mode. + +Each `detect_and_correct` pipeline comes with a corresponding [Query Trigger](https://flowpipe.io/docs/flowpipe-hcl/trigger/query), these are _disabled_ by default allowing for you to _enable_ and _schedule_ them as desired. + +Let's begin by looking at how to set-up a Query Trigger to automatically resolve our SQL databases that do not block public access. + +Firsty, we need to update our `flowpipe.fpvars` file to add or update the following variables - if we want to run our remediation `hourly` and automatically `apply` the corrections: + +```hcl +sql_databases_with_public_access_enabled_trigger_enabled = true +sql_databases_with_public_access_enabled_trigger_schedule = "1h" +sql_databases_with_public_access_enabled_default_action = "revoke_firewall_rule" +``` + +Now we'll need to start up our Flowpipe server: + +```sh +flowpipe server +``` + +This will run every hour and detect SQL databases that do not block public access and apply the corrections without further interaction! + +### Configure Variables + +Several pipelines have [input variables](https://flowpipe.io/docs/build/mod-variables#input-variables) that can be configured to better match your environment and requirements. + +Each variable has a default defined in its source file, e.g, `sql/sql_databases_with_public_access_enabled.fp` (or `variables.fp` for more generic variables), but these can be overwritten in several ways: + +The easiest approach is to setup your `flowpipe.fpvars` file, starting with the sample: + +```sh +cp flowpipe.fpvars.example flowpipe.fpvars +vi flowpipe.fpvars + +flowpipe pipeline run detect_and_correct_sql_databases_with_public_access_enabled +``` + +Alternatively, you can pass variables on the command line: + +```sh +flowpipe pipeline run detect_and_correct_sql_databases_with_public_access_enabled --var notifier=notifier.default +``` + +Or through environment variables: + +```sh +export FP_VAR_notifier="notifier.default" +flowpipe pipeline run detect_and_correct_sql_databases_with_public_access_enabled +``` + +For more information, please see [Passing Input Variables](https://flowpipe.io/docs/build/mod-variables#passing-input-variables) + +Finally, each detection pipeline has a corresponding [Query Trigger](https://flowpipe.io/docs/flowpipe-hcl/trigger/query), these are disabled by default allowing for you to configure only those which are required, see the [docs](https://hub.flowpipe.io/mods/turbot/azure_compliance/triggers) for more information. + +## Open Source & Contributing + +This repository is published under the [Apache 2.0 license](https://www.apache.org/licenses/LICENSE-2.0). Please see our [code of conduct](https://github.com/turbot/.github/blob/main/CODE_OF_CONDUCT.md). We look forward to collaborating with you! + +[Flowpipe](https://flowpipe.io) and [Steampipe](https://steampipe.io) are products produced from this open source software, exclusively by [Turbot HQ, Inc](https://turbot.com). They are distributed under our commercial terms. Others are allowed to make their own distribution of the software, but cannot use any of the Turbot trademarks, cloud services, etc. You can learn more in our [Open Source FAQ](https://turbot.com/open-source). + +## Get Involved + +**[Join #flowpipe on Slack →](https://turbot.com/community/join)** + +Want to help but don't know where to start? Pick up one of the `help wanted` issues: + +- [Flowpipe](https://github.com/turbot/flowpipe/labels/help%20wanted) +- [Azure Compliance Mod](https://github.com/turbot/flowpipe-mod-azure-compliance/labels/help%20wanted) diff --git a/locals.fp b/locals.fp new file mode 100644 index 0000000..c74ec14 --- /dev/null +++ b/locals.fp @@ -0,0 +1,42 @@ +// Tags +locals { + azure_compliance_common_tags = { + category = "Compliance" + plugin = "azure" + service = "Azure" + } +} + +// Consts +locals { + level_verbose = "verbose" + level_info = "info" + level_error = "error" + style_ok = "ok" + style_info = "info" + style_alert = "alert" +} + +locals { + notification_level_enum = ["verbose", "info", "error"] +} + +// Common Texts +locals { + description_resource = "The name of the resource" + description_database = "Database connection string." + description_approvers = "List of notifiers to be used for obtaining action/approval decisions." + description_connection = "Name of the connection to be used for any authenticated actions." + description_resource_group = "Azure Resource Group. Examples: my-rg, my-rg-123." + description_subscription_id = "Azure Subscription Id. Examples: d46d7416-f95f-4771-bbb5-529d4c766." + description_title = "Title of the resource, to be used as a display name." + description_max_concurrency = "The maximum concurrency to use for responding to detection items." + description_notifier = "The name of the notifier to use for sending notification messages." + description_notifier_level = "The verbosity level of notification messages to send. Valid options are 'verbose', 'info', 'error'." + description_default_action = "The default action to use when there are no approvers." + description_enabled_actions = "The list of enabled actions to provide to approvers for selection." + description_trigger_enabled = "If true, the trigger is enabled." + description_trigger_schedule = "If the trigger is enabled, run it on this schedule." + description_items = "A collection of detected resources to run corrective actions against." +} + diff --git a/mod.fp b/mod.fp index 8a112c3..7726277 100644 --- a/mod.fp +++ b/mod.fp @@ -4,7 +4,7 @@ mod "azure_compliance" { color = "#ea4335" documentation = file("./README.md") icon = "/images/mods/turbot/azure-compliance.svg" - categories = ["azure", "cost", "compliance", "public cloud"] + categories = ["azure", "compliance", "public cloud", "standard"] opengraph { title = "Azure Compliance Mod for Flowpipe" @@ -13,11 +13,14 @@ mod "azure_compliance" { } require { - mod "github.com/turbot/flowpipe-mod-azure" { - version = "*" + flowpipe { + min_version = "1.0.0" } mod "github.com/turbot/flowpipe-mod-detect-correct" { - version = "*" + version = "^1" + } + mod "github.com/turbot/flowpipe-mod-azure" { + version = "^1" } } } diff --git a/pipelines/appservice/appservice.fp b/pipelines/appservice/appservice.fp new file mode 100644 index 0000000..380529f --- /dev/null +++ b/pipelines/appservice/appservice.fp @@ -0,0 +1,5 @@ +locals { + appservice_common_tags = merge(local.azure_compliance_common_tags, { + service = "Azure/AppService" + }) +} diff --git a/pipelines/appservice/appservice_web_apps_register_with_active_directory_disabled.fp b/pipelines/appservice/appservice_web_apps_register_with_active_directory_disabled.fp new file mode 100644 index 0000000..d539497 --- /dev/null +++ b/pipelines/appservice/appservice_web_apps_register_with_active_directory_disabled.fp @@ -0,0 +1,319 @@ +locals { + appservice_web_apps_register_with_active_directory_disabled_query = <<-EOQ + select + concat(app.id, ' [', app.subscription_id, '/', app.resource_group, ']') as title, + app.id as id, + app.name, + app.resource_group, + app.subscription_id, + app._ctx ->> 'connection_name' as conn + from + azure_app_service_web_app as app + where + identity = '{}'; + EOQ + + appservice_web_apps_register_with_active_directory_disabled_enabled_actions_enum = ["skip", "register_active_directory"] + appservice_web_apps_register_with_active_directory_disabled_default_action_enum = ["notify", "skip", "register_active_directory"] +} + +variable "appservice_web_apps_register_with_active_directory_disabled_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/AppService" + } +} + +variable "appservice_web_apps_register_with_active_directory_disabled_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/AppService" + } +} + +variable "appservice_web_apps_register_with_active_directory_disabled_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/AppService" + } +} + +variable "appservice_web_apps_register_with_active_directory_disabled_enabled_actions" { + type = list(string) + description = "The list of enabled actions to provide to approvers for selection." + default = ["skip", "register_active_directory"] + + tags = { + folder = "Advanced/AppService" + } +} + +trigger "query" "detect_and_correct_appservice_web_apps_register_with_active_directory_disabled" { + title = "Detect & correct App Service web apps register with active directory disabled" + description = "Detect App Service web apps register with active directory disabled and then register with active directory." + tags = local.appservice_common_tags + + enabled = var.appservice_web_apps_register_with_active_directory_disabled_trigger_enabled + schedule = var.appservice_web_apps_register_with_active_directory_disabled_trigger_schedule + database = var.database + sql = local.appservice_web_apps_register_with_active_directory_disabled_query + + capture "insert" { + pipeline = pipeline.correct_appservice_web_apps_register_with_active_directory_disabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_appservice_web_apps_register_with_active_directory_disabled" { + title = "Detect & correct App Service web apps register with active directory disabled" + description = "Detect App Service web apps register with active directory disabled and then register with active directory." + tags = merge(local.appservice_common_tags, { recommended = "true" }) + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.appservice_web_apps_register_with_active_directory_disabled_default_action + enum = local.appservice_web_apps_register_with_active_directory_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.appservice_web_apps_register_with_active_directory_disabled_enabled_actions + enum = local.appservice_web_apps_register_with_active_directory_disabled_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.appservice_web_apps_register_with_active_directory_disabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_appservice_web_apps_register_with_active_directory_disabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_appservice_web_apps_register_with_active_directory_disabled" { + title = "Correct App Service web apps not registered with active directory" + description = "Register active directory for App Service web apps not registered with active directory." + tags = merge(local.appservice_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + name = string + resource_group = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.appservice_web_apps_register_with_active_directory_disabled_default_action + enum = local.appservice_web_apps_register_with_active_directory_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.appservice_web_apps_register_with_active_directory_disabled_enabled_actions + enum = local.appservice_web_apps_register_with_active_directory_disabled_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} App Services not registered with Active Directory." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.id => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_appservice_web_app_register_with_active_directory_disabled + args = { + title = each.value.title + name = each.value.name + resource_group = each.value.resource_group + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_appservice_web_app_register_with_active_directory_disabled" { + title = "Correct App Service web app not registered with active directory" + description = "Register active directory for a App Service web app not registered with active directory." + tags = merge(local.appservice_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "name" { + type = string + description = "The name of the App Service web app." + } + + param "resource_group" { + type = string + description = local.description_resource_group + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.appservice_web_apps_register_with_active_directory_disabled_default_action + enum = local.appservice_web_apps_register_with_active_directory_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.appservice_web_apps_register_with_active_directory_disabled_enabled_actions + enum = local.appservice_web_apps_register_with_active_directory_disabled_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected App Service web app ${param.title} unregistered with active directory." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped App Service web app ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "register_active_directory" = { + label = "Register active directory" + value = "register_active_directory" + style = local.style_alert + pipeline_ref = azure.pipeline.assign_appservice_webapp_identity + pipeline_args = { + resource_group = param.resource_group + subscription_id = param.subscription_id + app_name = param.name + conn = param.conn + } + success_msg = "Registered active directory for App Service web app ${param.title}." + error_msg = "Error registering active directory for App Service web app ${param.title}." + } + } + } + } +} + diff --git a/pipelines/appservice/appservice_web_apps_with_authentication_disabled.fp b/pipelines/appservice/appservice_web_apps_with_authentication_disabled.fp new file mode 100644 index 0000000..c7209a7 --- /dev/null +++ b/pipelines/appservice/appservice_web_apps_with_authentication_disabled.fp @@ -0,0 +1,320 @@ +locals { + appservice_web_apps_with_authentication_disabled_query = <<-EOQ + select + concat(app.id, ' [', app.subscription_id, '/', app.resource_group, ']') as title, + app.id as id, + app.name, + app.resource_group, + app.subscription_id, + app._ctx ->> 'connection_name' as conn + from + azure_app_service_web_app as app + where + not (auth_settings -> 'properties' ->> 'enabled') :: boolean; + EOQ + + appservice_web_apps_with_authentication_disabled_enabled_actions_enum = ["skip", "enable_web_app_authentication"] + appservice_web_apps_with_authentication_disabled_default_action_enum = ["notify", "skip", "enable_web_app_authentication"] +} + +variable "appservice_web_apps_with_authentication_disabled_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/AppService" + } +} + +variable "appservice_web_apps_with_authentication_disabled_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/AppService" + } +} + +variable "appservice_web_apps_with_authentication_disabled_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/AppService" + } +} + +variable "appservice_web_apps_with_authentication_disabled_enabled_actions" { + type = list(string) + description = "The list of enabled actions approvers can select" + default = ["skip", "enable_web_app_authentication"] + + tags = { + folder = "Advanced/AppService" + } +} + +trigger "query" "detect_and_correct_appservice_web_apps_with_authentication_disabled" { + title = "Detect & correct App Service web apps with authentication disabled" + description = "Detect App Service web apps with authentication disabled and then enable authentication." + tags = local.appservice_common_tags + + enabled = var.appservice_web_apps_with_authentication_disabled_trigger_enabled + schedule = var.appservice_web_apps_with_authentication_disabled_trigger_schedule + database = var.database + sql = local.appservice_web_apps_with_authentication_disabled_query + + capture "insert" { + pipeline = pipeline.correct_appservice_web_apps_with_authentication_disabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_appservice_web_apps_with_authentication_disabled" { + title = "Detect & correct App Service web apps with authentication disabled" + description = "Detect App Service web apps with authentication disabled and then enable authentication." + tags = local.appservice_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.appservice_web_apps_with_authentication_disabled_default_action + enum = local.appservice_web_apps_with_authentication_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.appservice_web_apps_with_authentication_disabled_enabled_actions + enum = local.appservice_web_apps_with_authentication_disabled_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.appservice_web_apps_with_authentication_disabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_appservice_web_apps_with_authentication_disabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_appservice_web_apps_with_authentication_disabled" { + title = "Correct App Service web apps with authentication disabled" + description = "Enable authentication for App Service web apps with authentication disabled." + tags = merge(local.appservice_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + name = string + resource_group = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.appservice_web_apps_with_authentication_disabled_default_action + enum = local.appservice_web_apps_with_authentication_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.appservice_web_apps_with_authentication_disabled_enabled_actions + enum = local.appservice_web_apps_with_authentication_disabled_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} App Service web app(s) with authentication disabled." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.id => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_appservice_webapp_with_authentication_disabled + args = { + title = each.value.title + name = each.value.name + resource_group = each.value.resource_group + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_appservice_webapp_with_authentication_disabled" { + title = "Correct App Service web app with authentication disabled" + description = "Enable authentication for a App Service web app with authentication disabled." + tags = merge(local.appservice_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "name" { + type = string + description = "The name of the App Service web app." + } + + param "resource_group" { + type = string + description = local.description_resource_group + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.appservice_web_apps_with_authentication_disabled_default_action + enum = local.appservice_web_apps_with_authentication_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.appservice_web_apps_with_authentication_disabled_enabled_actions + enum = local.appservice_web_apps_with_authentication_disabled_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected App Service web app ${param.title} with authentication disabled." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped App Service web app ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "enable_web_app_authentication" = { + label = "Enable web app authentication" + value = "enable_web_app_authentication" + style = local.style_alert + pipeline_ref = azure.pipeline.update_appservice_webapp_auth + pipeline_args = { + resource_group = param.resource_group + subscription_id = param.subscription_id + app_name = param.name + conn = param.conn + enabled = true + } + success_msg = "Enabled authentication for App Service web app ${param.title}." + error_msg = "Error enabling authentication for App Service web app ${param.title}." + } + } + } + } +} + diff --git a/pipelines/appservice/appservice_web_apps_with_ftp_deployment_enabled.fp b/pipelines/appservice/appservice_web_apps_with_ftp_deployment_enabled.fp new file mode 100644 index 0000000..a3f995f --- /dev/null +++ b/pipelines/appservice/appservice_web_apps_with_ftp_deployment_enabled.fp @@ -0,0 +1,321 @@ +locals { + appservice_web_apps_with_ftp_deployment_enabled_query = <<-EOQ + select + concat(app.id, ' [', app.subscription_id, '/', app.resource_group, ']') as title, + app.id as id, + app.name, + app.resource_group, + app.subscription_id, + app._ctx ->> 'connection_name' as conn + from + azure_app_service_web_app as app + where + configuration -> 'properties' ->> 'ftpsState' = 'AllAllowed'; + EOQ + + appservice_web_apps_with_ftp_deployment_enabled_enabled_actions_enum = ["skip", "disable_ftp_deployment"] + appservice_web_apps_with_ftp_deployment_enabled_default_action_enum = ["notify", "skip", "disable_ftp_deployment"] +} + +variable "appservice_web_apps_with_ftp_deployment_enabled_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/AppService" + } +} + +variable "appservice_web_apps_with_ftp_deployment_enabled_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/AppService" + } +} + +variable "appservice_web_apps_with_ftp_deployment_enabled_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/AppService" + } +} + +variable "appservice_web_apps_with_ftp_deployment_enabled_enabled_actions" { + type = list(string) + description = "The list of enabled actions approvers can select." + default = ["skip", "disable_ftp_deployment"] + + tags = { + folder = "Advanced/AppService" + } +} + +trigger "query" "detect_and_correct_appservice_web_apps_with_ftp_deployment_enabled" { + title = "Detect & correct App Service web apps with FTP deployment enabled" + description = "Detect App Service web apps with FTP deployment enabled and then disable FTP deployment." + tags = local.appservice_common_tags + + enabled = var.appservice_web_apps_with_ftp_deployment_enabled_trigger_enabled + schedule = var.appservice_web_apps_with_ftp_deployment_enabled_trigger_schedule + database = var.database + sql = local.appservice_web_apps_with_ftp_deployment_enabled_query + + capture "insert" { + pipeline = pipeline.correct_appservice_web_apps_with_ftp_deployment_enabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_appservice_web_apps_with_ftp_deployment_enabled" { + title = "Detect & correct App Service web apps with FTP deployment enabled" + description = "Detect App Service web apps with FTP deployment enabled and then disable FTP deployment." + tags = local.appservice_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.appservice_web_apps_with_ftp_deployment_enabled_default_action + enum = local.appservice_web_apps_with_ftp_deployment_enabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.appservice_web_apps_with_ftp_deployment_enabled_enabled_actions + enum = local.appservice_web_apps_with_ftp_deployment_enabled_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.appservice_web_apps_with_ftp_deployment_enabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_appservice_web_apps_with_ftp_deployment_enabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_appservice_web_apps_with_ftp_deployment_enabled" { + title = "Correct App Service web apps with FTP deployment enabled" + description = "Disable FTP deployment for App Service web apps with FTP deployment enabled." + tags = merge(local.appservice_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + name = string + resource_group = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.appservice_web_apps_with_ftp_deployment_enabled_default_action + enum = local.appservice_web_apps_with_ftp_deployment_enabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.appservice_web_apps_with_ftp_deployment_enabled_enabled_actions + enum = local.appservice_web_apps_with_ftp_deployment_enabled_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} App Services web app(s) with FTP deployment enabled." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.id => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_appservice_web_app_with_ftp_deployment_enabled + args = { + title = each.value.title + name = each.value.name + resource_group = each.value.resource_group + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_appservice_web_app_with_ftp_deployment_enabled" { + title = "Correct App Service web app with FTP deployment enabled" + description = "Disable FTP deployment for a App Service web app with FTP deployment enabled." + tags = merge(local.appservice_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "name" { + type = string + description = "The name of the App Service web app." + } + + param "resource_group" { + type = string + description = local.description_resource_group + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.appservice_web_apps_with_ftp_deployment_enabled_default_action + enum = local.appservice_web_apps_with_ftp_deployment_enabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.appservice_web_apps_with_ftp_deployment_enabled_enabled_actions + enum = local.appservice_web_apps_with_ftp_deployment_enabled_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected App Service web app ${param.title} with FTP deployment enabled." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped App Service web app ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "disable_ftp_deployment" = { + label = "Disable FTP deployment" + value = "disable_ftp_deployment" + style = local.style_alert + pipeline_ref = azure.pipeline.set_config_appservice_webapp + pipeline_args = { + resource_group = param.resource_group + subscription_id = param.subscription_id + app_name = param.name + conn = param.conn + ftps_state = "Disabled" + } + success_msg = "Disabled FTP deployment for App Service web app ${param.title}." + error_msg = "Error disabling FTP deployment for App Service web app ${param.title}." + } + } + } + } +} + + diff --git a/pipelines/appservice/appservice_web_apps_with_remote_debugging_enabled.fp b/pipelines/appservice/appservice_web_apps_with_remote_debugging_enabled.fp new file mode 100644 index 0000000..abf7b19 --- /dev/null +++ b/pipelines/appservice/appservice_web_apps_with_remote_debugging_enabled.fp @@ -0,0 +1,320 @@ +locals { + appservice_web_apps_with_remote_debugging_enabled_query = <<-EOQ + select + concat(id, ' [', subscription_id, '/', resource_group, ']') as title, + id as id, + name, + resource_group, + subscription_id, + _ctx ->> 'connection_name' as conn + from + azure_app_service_web_app + where + configuration -> 'properties' ->> 'remoteDebuggingEnabled' <> 'false'; + EOQ + + appservice_web_apps_with_remote_debugging_enabled_enabled_actions_enum = ["skip", "disable_remote_debugging"] + appservice_web_apps_with_remote_debugging_enabled_default_action_enum = ["notify", "skip", "disable_remote_debugging"] +} + +variable "appservice_web_apps_with_remote_debugging_enabled_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/AppService" + } +} + +variable "appservice_web_apps_with_remote_debugging_enabled_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/AppService" + } +} + +variable "appservice_web_apps_with_remote_debugging_enabled_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/AppService" + } +} + +variable "appservice_web_apps_with_remote_debugging_enabled_enabled_actions" { + type = list(string) + description = "The list of enabled actions approvers can select" + default = ["skip", "disable_remote_debugging"] + + tags = { + folder = "Advanced/AppService" + } +} + +trigger "query" "detect_and_correct_appservice_web_apps_with_remote_debugging_enabled" { + title = "Detect & correct App Service web apps with remote debugging enabled" + description = "Detect App Service web apps with remote debugging enabled and then disable remote debugging." + tags = local.appservice_common_tags + + enabled = var.appservice_web_apps_with_remote_debugging_enabled_trigger_enabled + schedule = var.appservice_web_apps_with_remote_debugging_enabled_trigger_schedule + database = var.database + sql = local.appservice_web_apps_with_remote_debugging_enabled_query + + capture "insert" { + pipeline = pipeline.correct_appservice_web_apps_with_remote_debugging_enabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_appservice_web_apps_with_remote_debugging_enabled" { + title = "Detect & correct App Service web apps with remote debugging enabled" + description = "Detect App Service web apps with remote debugging enabled and then disable remote debugging." + tags = merge(local.appservice_common_tags, { recommended = "true" }) + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.appservice_web_apps_with_remote_debugging_enabled_default_action + enum = local.appservice_web_apps_with_remote_debugging_enabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.appservice_web_apps_with_remote_debugging_enabled_enabled_actions + enum = local.appservice_web_apps_with_remote_debugging_enabled_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.appservice_web_apps_with_remote_debugging_enabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_appservice_web_apps_with_remote_debugging_enabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_appservice_web_apps_with_remote_debugging_enabled" { + title = "Correct App Service web apps with remote debugging enabled" + description = "Disable remote debugging for App Service web apps with remote debugging enabled." + tags = merge(local.appservice_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + name = string + resource_group = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.appservice_web_apps_with_remote_debugging_enabled_default_action + enum = local.appservice_web_apps_with_remote_debugging_enabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.appservice_web_apps_with_remote_debugging_enabled_enabled_actions + enum = local.appservice_web_apps_with_remote_debugging_enabled_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} App Service web app(s) with remote debugging enabled." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.id => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_appservice_webapp_with_remote_debugging_enabled + args = { + title = each.value.title + name = each.value.name + resource_group = each.value.resource_group + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_appservice_webapp_with_remote_debugging_enabled" { + title = "Correct App Service web app with remote debugging enabled" + description = "Disabled remote debugging for a App Service web app with remote debugging enabled." + tags = merge(local.appservice_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "name" { + type = string + description = "The name of the App Service web app." + } + + param "resource_group" { + type = string + description = local.description_resource_group + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.appservice_web_apps_with_remote_debugging_enabled_default_action + enum = local.appservice_web_apps_with_remote_debugging_enabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.appservice_web_apps_with_remote_debugging_enabled_enabled_actions + enum = local.appservice_web_apps_with_remote_debugging_enabled_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected App Service web app ${param.title} with remote debugging enabled." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped App Service web app ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "disable_remote_debugging" = { + label = "Disable web app remote debugging" + value = "disable_remote_debugging" + style = local.style_alert + pipeline_ref = azure.pipeline.set_config_appservice_webapp + pipeline_args = { + resource_group = param.resource_group + subscription_id = param.subscription_id + app_name = param.name + conn = param.conn + remote_debugging = false + } + success_msg = "Disabled remote debugging for App Service web app ${param.title}." + error_msg = "Error disabling remote debugging for App Service web app ${param.title}." + } + } + } + } +} + diff --git a/pipelines/appservice/appservice_web_apps_without_https_enabled.fp b/pipelines/appservice/appservice_web_apps_without_https_enabled.fp new file mode 100644 index 0000000..65ece8b --- /dev/null +++ b/pipelines/appservice/appservice_web_apps_without_https_enabled.fp @@ -0,0 +1,320 @@ +locals { + appservice_web_apps_without_https_enabled_query = <<-EOQ + select + concat(app.id, ' [', app.subscription_id, '/', app.resource_group, ']') as title, + app.id as id, + app.name, + app.resource_group, + app.subscription_id, + app._ctx ->> 'connection_name' as conn + from + azure_app_service_web_app as app + where + not https_only; + EOQ + + appservice_web_apps_without_https_enabled_enabled_actions_enum = ["skip", "enable_https"] + appservice_web_apps_without_https_enabled_default_action_enum = ["notify", "skip", "enable_https"] +} + +variable "appservice_web_apps_without_https_enabled_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/AppService" + } +} + +variable "appservice_web_apps_without_https_enabled_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/AppService" + } +} + +variable "appservice_web_apps_without_https_enabled_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/AppService" + } +} + +variable "appservice_web_apps_without_https_enabled_enabled_actions" { + type = list(string) + description = "The list of enabled actions approvers can select." + default = ["skip", "enable_https"] + + tags = { + folder = "Advanced/AppService" + } +} + +trigger "query" "detect_and_correct_appservice_web_apps_without_https_enabled" { + title = "Detect & correct App Service web apps without HTTPS enabled" + description = "Detect App Services web apps without HTTPS enabled and then enable HTTPS." + tags = local.appservice_common_tags + + enabled = var.appservice_web_apps_without_https_enabled_trigger_enabled + schedule = var.appservice_web_apps_without_https_enabled_trigger_schedule + database = var.database + sql = local.appservice_web_apps_without_https_enabled_query + + capture "insert" { + pipeline = pipeline.correct_appservice_web_apps_without_https_enabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_appservice_web_apps_without_https_enabled" { + title = "Detect & correct App Service web apps without HTTPS enabled" + description = "Detect App Service web apps without HTTPS enabled and then enable HTTPS." + tags = merge(local.appservice_common_tags, { recommended = "true" }) + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.appservice_web_apps_without_https_enabled_default_action + enum = local.appservice_web_apps_without_https_enabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.appservice_web_apps_without_https_enabled_enabled_actions + enum = local.appservice_web_apps_without_https_enabled_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.appservice_web_apps_without_https_enabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_appservice_web_apps_without_https_enabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_appservice_web_apps_without_https_enabled" { + title = "Correct App Service web apps without HTTPS enabled" + description = "Enable HTTPS for App Service web apps without HTTPS enabled." + tags = merge(local.appservice_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + name = string + resource_group = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.appservice_web_apps_without_https_enabled_default_action + enum = local.appservice_web_apps_without_https_enabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.appservice_web_apps_without_https_enabled_enabled_actions + enum = local.appservice_web_apps_without_https_enabled_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} App Service web app(s) without HTTPS enabled." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.id => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_appservice_web_app_without_https + args = { + title = each.value.title + name = each.value.name + resource_group = each.value.resource_group + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_appservice_web_app_without_https" { + title = "Correct App Service web app without HTTPS enabled" + description = "Enable HTTPS for a App Service web app without HTTPS enabled." + tags = merge(local.appservice_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "name" { + type = string + description = "The name of the App Service web app." + } + + param "resource_group" { + type = string + description = local.description_resource_group + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.appservice_web_apps_without_https_enabled_default_action + enum = local.appservice_web_apps_without_https_enabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.appservice_web_apps_without_https_enabled_enabled_actions + enum = local.appservice_web_apps_without_https_enabled_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected App Service web app ${param.title} without HTTPS enabled." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped App Service web app ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "enable_https" = { + label = "Enable HTTPS" + value = "enable_https" + style = local.style_alert + pipeline_ref = azure.pipeline.update_appservice_webapp + pipeline_args = { + resource_group = param.resource_group + subscription_id = param.subscription_id + app_name = param.name + conn = param.conn + https_only = true + } + success_msg = "Enabled HTTPS for App Service web app ${param.title}." + error_msg = "Error enabling HTTPS for App Service web app ${param.title}." + } + } + } + } +} + diff --git a/pipelines/appservice/appservice_web_apps_without_latest_http_version.fp b/pipelines/appservice/appservice_web_apps_without_latest_http_version.fp new file mode 100644 index 0000000..08b3198 --- /dev/null +++ b/pipelines/appservice/appservice_web_apps_without_latest_http_version.fp @@ -0,0 +1,320 @@ +locals { + appservice_web_apps_without_latest_http_version_query = <<-EOQ + select + concat(app.id, ' [', app.subscription_id, '/', app.resource_group, ']') as title, + app.id as id, + app.name, + app.resource_group, + app.subscription_id, + app._ctx ->> 'connection_name' as conn + from + azure_app_service_web_app as app + where + not (configuration -> 'properties' ->> 'http20Enabled') :: boolean; + EOQ + + appservice_web_apps_without_latest_http_version_enabled_actions_enum = ["skip", "enable_latest_http_version"] + appservice_web_apps_without_latest_http_version_default_action_enum = ["notify", "skip", "enable_latest_http_version"] +} + +variable "appservice_web_apps_without_latest_http_version_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/AppService" + } +} + +variable "appservice_web_apps_without_latest_http_version_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/AppService" + } +} + +variable "appservice_web_apps_without_latest_http_version_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/AppService" + } +} + +variable "appservice_web_apps_without_latest_http_version_enabled_actions" { + type = list(string) + description = "The list of enabled actions approvers can select." + default = ["skip", "enable_latest_http_version"] + + tags = { + folder = "Advanced/AppService" + } +} + +trigger "query" "detect_and_correct_appservice_web_apps_without_latest_http_version" { + title = "Detect & correct App Service web apps without latest HTTP version" + description = "Detect App Services web apps without latest HTTP version and then enable latest HTTP version." + tags = local.appservice_common_tags + + enabled = var.appservice_web_apps_without_latest_http_version_trigger_enabled + schedule = var.appservice_web_apps_without_latest_http_version_trigger_schedule + database = var.database + sql = local.appservice_web_apps_without_latest_http_version_query + + capture "insert" { + pipeline = pipeline.correct_appservice_web_apps_without_latest_http_version + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_appservice_web_apps_without_latest_http_version" { + title = "Detect & correct App Service web apps without latest HTTP version" + description = "Detect App Services web apps without latest HTTP version and then enable latest HTTP version." + tags = local.appservice_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.appservice_web_apps_without_latest_http_version_default_action + enum = local.appservice_web_apps_without_latest_http_version_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.appservice_web_apps_without_latest_http_version_enabled_actions + enum = local.appservice_web_apps_without_latest_http_version_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.appservice_web_apps_without_latest_http_version_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_appservice_web_apps_without_latest_http_version + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_appservice_web_apps_without_latest_http_version" { + title = "Correct App Services web apps without latest HTTP version" + description = "Enable latest HTTP version for App Services web apps without latest HTTP version." + tags = merge(local.appservice_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + name = string + resource_group = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.appservice_web_apps_without_latest_http_version_default_action + enum = local.appservice_web_apps_without_latest_http_version_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.appservice_web_apps_without_latest_http_version_enabled_actions + enum = local.appservice_web_apps_without_latest_http_version_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} App Services web app(s) without latest HTTP version." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.id => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_appservice_web_app_without_latest_http_version + args = { + title = each.value.title + name = each.value.name + resource_group = each.value.resource_group + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_appservice_web_app_without_latest_http_version" { + title = "Correct App Services web app without latest HTTP version" + description = "Enable latest HTTP version for a App Services web app without latest HTTP version." + tags = merge(local.appservice_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "name" { + type = string + description = "The name of the App Service web app." + } + + param "resource_group" { + type = string + description = local.description_resource_group + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.appservice_web_apps_without_latest_http_version_default_action + enum = local.appservice_web_apps_without_latest_http_version_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.appservice_web_apps_without_latest_http_version_enabled_actions + enum = local.appservice_web_apps_without_latest_http_version_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected App Service web app ${param.title} without latest HTTP version." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped App Service web app ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "enable_latest_http_version" = { + label = "Enable latest HTTP version" + value = "enable_latest_http_version" + style = local.style_alert + pipeline_ref = azure.pipeline.set_config_appservice_webapp + pipeline_args = { + resource_group = param.resource_group + subscription_id = param.subscription_id + app_name = param.name + conn = param.conn + enable_http2 = true + } + success_msg = "Enabled latest HTTP version for App Service web app ${param.title}." + error_msg = "Error enabling latest HTTP version for App Service web app ${param.title}." + } + } + } + } +} + diff --git a/pipelines/appservice/appservice_web_apps_without_latest_php_version.fp b/pipelines/appservice/appservice_web_apps_without_latest_php_version.fp new file mode 100644 index 0000000..2c562a4 --- /dev/null +++ b/pipelines/appservice/appservice_web_apps_without_latest_php_version.fp @@ -0,0 +1,364 @@ +locals { + appservice_web_apps_without_latest_php_version_query = <<-EOQ + select + concat(id, ' [', subscription_id, '/', resource_group, ']') as title, + id as id, + name, + resource_group, + subscription_id, + _ctx ->> 'connection_name' as conn + from + azure_app_service_web_app + where + exists ( + select + from + unnest(regexp_split_to_array(kind, ',')) elem + where + elem like 'app%' + ) + and exists ( + select + from + unnest(regexp_split_to_array(kind, ',')) elem + where + elem = 'linux' + ) + and configuration -> 'properties' ->> 'linuxFxVersion' like 'PHP%' + and configuration -> 'properties' ->> 'linuxFxVersion' <> 'PHP|8.3'; + EOQ + + appservice_web_apps_without_latest_php_version_enabled_actions_enum = ["skip", "enable_latest_php_version"] + appservice_web_apps_without_latest_php_version_default_action_enum = ["notify", "skip", "enable_latest_php_version"] +} + +variable "appservice_web_apps_without_latest_php_version_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/AppService" + } +} + +variable "appservice_web_apps_without_latest_php_version_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/AppService" + } +} + +variable "appservice_web_apps_without_latest_php_version_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/AppService" + } +} + +variable "appservice_web_apps_without_latest_php_version_enabled_actions" { + type = list(string) + description = "The list of enabled actions approvers can select." + default = ["skip", "enable_latest_php_version"] + + tags = { + folder = "Advanced/AppService" + } +} + +variable "appservice_web_apps_without_latest_php_version_linux_fx_version" { + type = string + description = "The linux fx version for App Service web app." + default = "PHP|8.3" + + tags = { + folder = "Advanced/AppService" + } +} + +trigger "query" "detect_and_correct_appservice_web_apps_without_latest_php_version" { + title = "Detect & correct App Service web apps without the latest PHP version" + description = "Detect App Services web apps without the latest PHP version and then enable latest PHP version." + tags = local.appservice_common_tags + + enabled = var.appservice_web_apps_without_latest_php_version_trigger_enabled + schedule = var.appservice_web_apps_without_latest_php_version_trigger_schedule + database = var.database + sql = local.appservice_web_apps_without_latest_php_version_query + + capture "insert" { + pipeline = pipeline.correct_appservice_web_apps_without_latest_php_version + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_appservice_web_apps_without_latest_php_version" { + title = "Detect & correct App Service web apps without the latest PHP version" + description = "Detect App Services web apps without the latest PHP version and then enable latest PHP version." + tags = local.appservice_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "linux_fx_version" { + type = string + description = "The linux fx version for App Service web app." + default = var.appservice_web_apps_without_latest_php_version_linux_fx_version + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.appservice_web_apps_without_latest_php_version_default_action + enum = local.appservice_web_apps_without_latest_php_version_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.appservice_web_apps_without_latest_php_version_enabled_actions + enum = local.appservice_web_apps_without_latest_php_version_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.appservice_web_apps_without_latest_php_version_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_appservice_web_apps_without_latest_php_version + args = { + items = step.query.detect.rows + linux_fx_version = param.linux_fx_version + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_appservice_web_apps_without_latest_php_version" { + title = "Correct App Services web apps without the latest PHP version" + description = "Enable latest PHP version for App Services web apps without the latest PHP version." + tags = merge(local.appservice_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + name = string + resource_group = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "linux_fx_version" { + type = string + description = "The linux fx version for App Service web app." + default = var.appservice_web_apps_without_latest_php_version_linux_fx_version + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.appservice_web_apps_without_latest_php_version_default_action + enum = local.appservice_web_apps_without_latest_php_version_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.appservice_web_apps_without_latest_php_version_enabled_actions + enum = local.appservice_web_apps_without_latest_php_version_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} App Services web app(s) without the latest PHP version." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.id => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_appservice_web_app_without_latest_php_version + args = { + title = each.value.title + name = each.value.name + resource_group = each.value.resource_group + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + linux_fx_version = param.linux_fx_version + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_appservice_web_app_without_latest_php_version" { + title = "Correct App Services web app without the latest PHP version" + description = "Enable latest PHP version for a App Services web app without the latest PHP version." + tags = merge(local.appservice_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "name" { + type = string + description = "The name of the App Service web app." + } + + param "resource_group" { + type = string + description = local.description_resource_group + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "linux_fx_version" { + type = string + description = "The linux fx version for App Service web app." + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.appservice_web_apps_without_latest_php_version_default_action + enum = local.appservice_web_apps_without_latest_php_version_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.appservice_web_apps_without_latest_php_version_enabled_actions + enum = local.appservice_web_apps_without_latest_php_version_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected App Service web app ${param.title} without the latest PHP version." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped App Service web app ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "enable_latest_php_version" = { + label = "Enable latest PHP version" + value = "enable_latest_php_version" + style = local.style_alert + pipeline_ref = azure.pipeline.set_config_appservice_webapp + pipeline_args = { + resource_group = param.resource_group + subscription_id = param.subscription_id + app_name = param.name + conn = param.conn + linux_fx_version = param.linux_fx_version + } + success_msg = "Enabled latest PHP version for App Service web app ${param.title}." + error_msg = "Error enabling latest PHP version for App Service web app ${param.title}." + } + } + } + } +} + diff --git a/pipelines/appservice/appservice_web_apps_without_latest_python_version.fp b/pipelines/appservice/appservice_web_apps_without_latest_python_version.fp new file mode 100644 index 0000000..4b4b75c --- /dev/null +++ b/pipelines/appservice/appservice_web_apps_without_latest_python_version.fp @@ -0,0 +1,394 @@ +locals { + appservice_web_apps_without_latest_python_version_query = <<-EOQ + select + concat(id, ' [', subscription_id, '/', resource_group, ']') as title, + id as id, + name, + resource_group, + subscription_id, + _ctx ->> 'connection_name' as conn + from + azure_app_service_web_app + where + exists ( + select + from + unnest(regexp_split_to_array(kind, ',')) elem + where + elem like 'app%' + ) + and exists ( + select + from + unnest(regexp_split_to_array(kind, ',')) elem + where + elem = 'linux' + ) + and configuration -> 'properties' ->> 'linuxFxVersion' like 'PYTHON%' + and configuration -> 'properties' ->> 'linuxFxVersion' <> 'PYTHON|3.12'; + EOQ + + appservice_web_apps_without_latest_python_version_enabled_actions_enum = ["skip", "enable_latest_python_version"] + appservice_web_apps_without_latest_python_version_default_action_enum = ["notify", "skip", "enable_latest_python_version"] +} + +variable "appservice_web_apps_without_latest_python_version_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/AppService" + } +} + +variable "appservice_web_apps_without_latest_python_version_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/AppService" + } +} + +variable "appservice_web_apps_without_latest_python_version_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/AppService" + } +} + +variable "appservice_web_apps_without_latest_python_version_enabled_actions" { + type = list(string) + description = "The list of enabled actions approvers can select." + default = ["skip", "enable_latest_python_version"] + + tags = { + folder = "Advanced/AppService" + } +} + +variable "appservice_web_apps_without_latest_python_version_python_version" { + type = string + description = "The python version for App Service web app." + default = "3.12" + + tags = { + folder = "Advanced/AppService" + } +} + +variable "appservice_web_apps_without_latest_python_version_linux_fx_version" { + type = string + description = "The linux fx version for App Service web app." + default = "PYTHON|3.12" + + tags = { + folder = "Advanced/AppService" + } +} + +trigger "query" "detect_and_correct_appservice_web_apps_without_latest_python_version" { + title = "Detect & correct App Service web apps without the latest python version" + description = "Detect App Services web apps without the latest python version and then enable latest python version." + tags = local.appservice_common_tags + + enabled = var.appservice_web_apps_without_latest_python_version_trigger_enabled + schedule = var.appservice_web_apps_without_latest_python_version_trigger_schedule + database = var.database + sql = local.appservice_web_apps_without_latest_python_version_query + + capture "insert" { + pipeline = pipeline.correct_appservice_web_apps_without_latest_python_version + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_appservice_web_apps_without_latest_python_version" { + title = "Detect & correct App Service web apps without the latest python version" + description = "Detect App Services web apps without the latest python version and then enable latest python version." + tags = local.appservice_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "python_version" { + type = string + description = "The python version for App Service web app." + default = var.appservice_web_apps_without_latest_python_version_python_version + } + + param "linux_fx_version" { + type = string + description = "The linux fx version for App Service web app." + default = var.appservice_web_apps_without_latest_python_version_linux_fx_version + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.appservice_web_apps_without_latest_python_version_default_action + enum = local.appservice_web_apps_without_latest_python_version_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.appservice_web_apps_without_latest_python_version_enabled_actions + enum = local.appservice_web_apps_without_latest_python_version_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.appservice_web_apps_without_latest_python_version_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_appservice_web_apps_without_latest_python_version + args = { + items = step.query.detect.rows + python_version = param.python_version + linux_fx_version = param.linux_fx_version + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_appservice_web_apps_without_latest_python_version" { + title = "Correct App Services web apps without the latest python version" + description = "Enable latest python version for App Services web apps without the latest python version." + tags = merge(local.appservice_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + name = string + resource_group = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "python_version" { + type = string + description = "The python version for App Service web app." + default = var.appservice_web_apps_without_latest_python_version_python_version + } + + param "linux_fx_version" { + type = string + description = "The linux fx version for App Service web app." + default = var.appservice_web_apps_without_latest_python_version_linux_fx_version + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.appservice_web_apps_without_latest_python_version_default_action + enum = local.appservice_web_apps_without_latest_python_version_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.appservice_web_apps_without_latest_python_version_enabled_actions + enum = local.appservice_web_apps_without_latest_python_version_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} App Services web app(s) without the latest python version." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.id => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_appservice_web_app_without_latest_python_version + args = { + title = each.value.title + name = each.value.name + resource_group = each.value.resource_group + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + python_version = param.python_version + linux_fx_version = param.linux_fx_version + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_appservice_web_app_without_latest_python_version" { + title = "Correct App Services web app without the latest python version" + description = "Enable latest python version for a App Services web app without the latest python version." + tags = merge(local.appservice_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "name" { + type = string + description = "The name of the App Service web app." + } + + param "resource_group" { + type = string + description = local.description_resource_group + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "python_version" { + type = string + description = "The python version for App Service web app." + } + + param "linux_fx_version" { + type = string + description = "The linux fx version for App Service web app." + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.appservice_web_apps_without_latest_python_version_default_action + enum = local.appservice_web_apps_without_latest_python_version_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.appservice_web_apps_without_latest_python_version_enabled_actions + enum = local.appservice_web_apps_without_latest_python_version_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected App Service web app ${param.title} without the latest python version." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped App Service web app ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "enable_latest_python_version" = { + label = "Enable latest python version" + value = "enable_latest_python_version" + style = local.style_alert + pipeline_ref = azure.pipeline.set_config_appservice_webapp + pipeline_args = { + resource_group = param.resource_group + subscription_id = param.subscription_id + app_name = param.name + conn = param.conn + python_version = param.python_version + linux_fx_version = param.linux_fx_version + } + success_msg = "Enabled latest python version for App Service web app ${param.title}." + error_msg = "Error enabling latest python version for App Service web app ${param.title}." + } + } + } + } +} + diff --git a/pipelines/appservice/appservice_web_apps_without_latest_tls_version.fp b/pipelines/appservice/appservice_web_apps_without_latest_tls_version.fp new file mode 100644 index 0000000..670b6eb --- /dev/null +++ b/pipelines/appservice/appservice_web_apps_without_latest_tls_version.fp @@ -0,0 +1,320 @@ +locals { + appservice_web_apps_without_latest_tls_version_query = <<-EOQ + select + concat(app.id, ' [', app.subscription_id, '/', app.resource_group, ']') as title, + app.id as id, + app.name, + app.resource_group, + app.subscription_id, + app._ctx ->> 'connection_name' as conn + from + azure_app_service_web_app as app + where + configuration -> 'properties' ->> 'minTlsVersion' < '1.2'; + EOQ + + appservice_web_apps_without_latest_tls_version_enabled_actions_enum = ["skip", "enable_latest_tls_version"] + appservice_web_apps_without_latest_tls_version_default_action_enum = ["notify", "skip", "enable_latest_tls_version"] +} + +variable "appservice_web_apps_without_latest_tls_version_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/AppService" + } +} + +variable "appservice_web_apps_without_latest_tls_version_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/AppService" + } +} + +variable "appservice_web_apps_without_latest_tls_version_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/AppService" + } +} + +variable "appservice_web_apps_without_latest_tls_version_enabled_actions" { + type = list(string) + description = "The list of enabled actions to provide to approvers for selection." + default = ["skip", "enable_latest_tls_version"] + + tags = { + folder = "Advanced/AppService" + } +} + +trigger "query" "detect_and_correct_appservice_web_apps_without_latest_tls_version" { + title = "Detect & correct App Service web apps without the latest TLS version" + description = "Detect App Services web apps without the latest TLS version and then enable latest TLS version." + tags = local.appservice_common_tags + + enabled = var.appservice_web_apps_without_latest_tls_version_trigger_enabled + schedule = var.appservice_web_apps_without_latest_tls_version_trigger_schedule + database = var.database + sql = local.appservice_web_apps_without_latest_tls_version_query + + capture "insert" { + pipeline = pipeline.correct_appservice_web_apps_without_latest_tls_version + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_appservice_web_apps_without_latest_tls_version" { + title = "Detect & correct App Service web apps without the latest TLS version" + description = "Detect App Services web apps without the latest TLS version and then enable latest TLS version." + tags = local.appservice_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.appservice_web_apps_without_latest_tls_version_default_action + enum = local.appservice_web_apps_without_latest_tls_version_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.appservice_web_apps_without_latest_tls_version_enabled_actions + enum = local.appservice_web_apps_without_latest_tls_version_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.appservice_web_apps_without_latest_tls_version_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_appservice_web_apps_without_latest_tls_version + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_appservice_web_apps_without_latest_tls_version" { + title = "Correct App Service web apps without the latest TLS version" + description = "Enable latest TLS version for App Service web apps without the latest TLS version." + tags = merge(local.appservice_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + name = string + resource_group = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.appservice_web_apps_without_latest_tls_version_default_action + enum = local.appservice_web_apps_without_latest_tls_version_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.appservice_web_apps_without_latest_tls_version_enabled_actions + enum = local.appservice_web_apps_without_latest_tls_version_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} App Services web app(s) without the latest TLS version." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.id => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_appservice_web_app_without_latest_tls_version + args = { + title = each.value.title + name = each.value.name + resource_group = each.value.resource_group + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_appservice_web_app_without_latest_tls_version" { + title = "Correct App Service web app without the latest TLS version" + description = "Enable latest TLS version for a App Service web app without the latest TLS version." + tags = merge(local.appservice_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "name" { + type = string + description = "The name of the App Service web app." + } + + param "resource_group" { + type = string + description = local.description_resource_group + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.appservice_web_apps_without_latest_tls_version_default_action + enum = local.appservice_web_apps_without_latest_tls_version_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.appservice_web_apps_without_latest_tls_version_enabled_actions + enum = local.appservice_web_apps_without_latest_tls_version_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected App Service web app ${param.title} without the latest TLS version." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped App Service web app ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "enable_latest_tls_version" = { + label = "Enable latest TLS version" + value = "enable_latest_tls_version" + style = local.style_alert + pipeline_ref = azure.pipeline.set_config_appservice_webapp + pipeline_args = { + resource_group = param.resource_group + subscription_id = param.subscription_id + app_name = param.name + conn = param.conn + tls_version = "1.2" + } + success_msg = "Enabled latest TLS version for App Service web app ${param.title}." + error_msg = "Error enabling latest TLS version for App Service web app ${param.title}." + } + } + } + } +} + diff --git a/pipelines/compute/compute.fp b/pipelines/compute/compute.fp new file mode 100644 index 0000000..533802f --- /dev/null +++ b/pipelines/compute/compute.fp @@ -0,0 +1,5 @@ +locals { + compute_common_tags = merge(local.azure_compliance_common_tags, { + service = "Azure/Compute" + }) +} \ No newline at end of file diff --git a/pipelines/compute/compute_attached_disks_not_encrypted_with_cmk.fp b/pipelines/compute/compute_attached_disks_not_encrypted_with_cmk.fp new file mode 100644 index 0000000..9c4e332 --- /dev/null +++ b/pipelines/compute/compute_attached_disks_not_encrypted_with_cmk.fp @@ -0,0 +1,132 @@ +locals { + compute_attached_disks_not_encrypted_with_cmk_query = <<-EOQ + select + concat(id, ' [', subscription_id, '/', resource_group, ']') as title, + name, + resource_group, + subscription_id, + _ctx ->> 'connection_name' as conn + from + azure_compute_disk + where + disk_state = 'Attached' + and encryption_type <> 'EncryptionAtRestWithCustomerKey'; + EOQ +} + +variable "compute_attached_disks_not_encrypted_with_cmk_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/Compute" + } +} + +variable "compute_attached_disks_not_encrypted_with_cmk_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/Compute" + } +} + +trigger "query" "detect_and_correct_compute_attached_disks_not_encrypted_with_cmk" { + title = "Detect & correct Compute disks not encrypted with CMK" + description = "Detect Compute disks not encrypted with CMK then encrypt with CMK." + tags = local.compute_common_tags + + enabled = var.compute_attached_disks_not_encrypted_with_cmk_trigger_enabled + schedule = var.compute_attached_disks_not_encrypted_with_cmk_trigger_schedule + database = var.database + sql = local.compute_attached_disks_not_encrypted_with_cmk_query + + capture "insert" { + pipeline = pipeline.correct_compute_attached_disks_not_encrypted_with_cmk + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_compute_attached_disks_not_encrypted_with_cmk" { + title = "Detect & correct Compute disks not encrypted with CMK" + description = "Detect Compute disks not encrypted with CMK then encrypt with CMK." + tags = local.compute_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.compute_attached_disks_not_encrypted_with_cmk_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_compute_attached_disks_not_encrypted_with_cmk + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_compute_attached_disks_not_encrypted_with_cmk" { + title = "Correct Compute disks not encrypted with CMK" + description = "Encrypt Compute disks with CMK for disks not encrypted with CMK." + tags = merge(local.compute_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} Compute disk(s) not encrypted with CMK." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected Compute disk ${each.value.title} not encrypted with CMK." + } +} diff --git a/pipelines/compute/compute_disks_with_data_access_auth_mode_disabled.fp b/pipelines/compute/compute_disks_with_data_access_auth_mode_disabled.fp new file mode 100644 index 0000000..d646f5e --- /dev/null +++ b/pipelines/compute/compute_disks_with_data_access_auth_mode_disabled.fp @@ -0,0 +1,319 @@ +locals { + compute_disks_with_data_access_auth_mode_disabled_query = <<-EOQ + select + concat(id, ' [', subscription_id, '/', resource_group, ']') as title, + name, + resource_group, + subscription_id, + _ctx ->> 'connection_name' as conn + from + azure_compute_disk + where + data_access_auth_mode <> 'AzureActiveDirectory'; + EOQ + + compute_disks_with_data_access_auth_mode_disabled_enabled_actions_enum = ["skip", "enable_data_access_auth_mode"] + compute_disks_with_data_access_auth_mode_disabled_default_action_enum = ["notify", "skip", "enable_data_access_auth_mode"] +} + +variable "compute_disks_with_data_access_auth_mode_disabled_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/Compute" + } +} + +variable "compute_disks_with_data_access_auth_mode_disabled_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/Compute" + } +} + +variable "compute_disks_with_data_access_auth_mode_disabled_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/Compute" + } +} + +variable "compute_disks_with_data_access_auth_mode_disabled_enabled_actions" { + type = list(string) + description = "The list of enabled actions to provide to approvers for selection." + default = ["skip", "enable_data_access_auth_mode"] + + tags = { + folder = "Advanced/Compute" + } +} + +trigger "query" "detect_and_correct_compute_disks_with_data_access_auth_mode_disabled" { + title = "Detect & correct Compute disks with data access auth mode disabled" + description = "Detect Compute disks with data access auth mode disabled and then enable data access auth mode." + tags = local.compute_common_tags + + enabled = var.compute_disks_with_data_access_auth_mode_disabled_trigger_enabled + schedule = var.compute_disks_with_data_access_auth_mode_disabled_trigger_schedule + database = var.database + sql = local.compute_disks_with_data_access_auth_mode_disabled_query + + capture "insert" { + pipeline = pipeline.correct_compute_disks_with_data_access_auth_mode_disabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_compute_disks_with_data_access_auth_mode_disabled" { + title = "Detect & correct Compute disks with data access auth mode disabled" + description = "Detect Compute disks with data access auth mode disabled and then enable data access auth mode." + tags = merge(local.compute_common_tags, { recommended = "true" }) + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.compute_disks_with_data_access_auth_mode_disabled_default_action + enum = local.compute_disks_with_data_access_auth_mode_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.compute_disks_with_data_access_auth_mode_disabled_enabled_actions + enum = local.compute_disks_with_data_access_auth_mode_disabled_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.compute_disks_with_data_access_auth_mode_disabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_compute_disks_with_data_access_auth_mode_disabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_compute_disks_with_data_access_auth_mode_disabled" { + title = "Correct Compute disks with data access auth mode disabled" + description = "Enable data access auth mode for Compute disks with data access auth mode disabled." + tags = merge(local.compute_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + name = string + resource_group = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.compute_disks_with_data_access_auth_mode_disabled_default_action + enum = local.compute_disks_with_data_access_auth_mode_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.compute_disks_with_data_access_auth_mode_disabled_enabled_actions + enum = local.compute_disks_with_data_access_auth_mode_disabled_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} Compute disk(s) with data access auth mode disabled." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.title => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_compute_disk_with_data_access_auth_mode_disabled + args = { + title = each.value.title + name = each.value.name + resource_group = each.value.resource_group + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_compute_disk_with_data_access_auth_mode_disabled" { + title = "Correct Compute disk with data access auth mode disabled" + description = "Enable data access auth mode for Compute disk with data access auth mode disabled." + tags = merge(local.compute_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "name" { + type = string + description = "The name of the Compute server." + } + + param "resource_group" { + type = string + description = local.description_resource_group + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.compute_disks_with_data_access_auth_mode_disabled_default_action + enum = local.compute_disks_with_data_access_auth_mode_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.compute_disks_with_data_access_auth_mode_disabled_enabled_actions + enum = local.compute_disks_with_data_access_auth_mode_disabled_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected Compute disk ${param.title} with data access auth mode disabled." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped Compute disk ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "enable_data_access_auth_mode" = { + label = "Enable data access auth mode" + value = "enable_data_access_auth_mode" + style = local.style_alert + pipeline_ref = azure.pipeline.update_compute_disk + pipeline_args = { + disk_name = param.name + resource_group = param.resource_group + subscription_id = param.subscription_id + conn = param.conn + data_access_auth_mode = true + } + success_msg = "Enabled data access auth mode for Compute disk ${param.title}." + error_msg = "Error enabling data access auth mode for Compute disk ${param.title}." + } + } + } + } +} + + diff --git a/pipelines/compute/compute_disks_with_public_access_enabled.fp b/pipelines/compute/compute_disks_with_public_access_enabled.fp new file mode 100644 index 0000000..f35e151 --- /dev/null +++ b/pipelines/compute/compute_disks_with_public_access_enabled.fp @@ -0,0 +1,135 @@ +locals { + compute_disks_with_public_access_enabled_query = <<-EOQ + select + concat(id, ' [', subscription_id, '/', resource_group, ']') as title, + name, + resource_group, + subscription_id, + _ctx ->> 'connection_name' as conn + from + azure_compute_disk + where + network_access_policy not in ('DenyAll','AllowPrivate') and public_network_access = 'Enabled'; + EOQ + + compute_disks_with_public_access_enabled_enabled_actions_enum = ["skip", "disable_public_access"] + compute_disks_with_public_access_enabled_default_action_enum = ["notify", "skip", "c"] +} + +variable "compute_disks_with_public_access_enabled_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/Compute" + } +} + +variable "compute_disks_with_public_access_enabled_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/Compute" + } +} + +trigger "query" "detect_and_correct_compute_disks_with_public_access_enabled" { + title = "Detect & correct Compute disks with public access enabled" + description = "Detect Compute disks with public access enabled then disable public access." + tags = local.compute_common_tags + + enabled = var.compute_disks_with_public_access_enabled_trigger_enabled + schedule = var.compute_disks_with_public_access_enabled_trigger_schedule + database = var.database + sql = local.compute_disks_with_public_access_enabled_query + + capture "insert" { + pipeline = pipeline.correct_compute_disks_with_public_access_enabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_compute_disks_with_public_access_enabled" { + title = "Detect & correct Compute disks with public access enabled" + description = "Detect Compute disks with public access enabled then disable public access." + tags = local.compute_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.compute_disks_with_public_access_enabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_compute_disks_with_public_access_enabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_compute_disks_with_public_access_enabled" { + title = "Correct Compute disks with public access enabled" + description = "Disable public access on a collection of Compute disks with public access enabled." + tags = merge(local.compute_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} Compute disk(s) with public access enabled." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected Compute disk ${each.value.title} with public access enabled." + } +} + diff --git a/pipelines/compute/compute_unattached_disks_not_encrypted_with_cmk.fp b/pipelines/compute/compute_unattached_disks_not_encrypted_with_cmk.fp new file mode 100644 index 0000000..1b08e86 --- /dev/null +++ b/pipelines/compute/compute_unattached_disks_not_encrypted_with_cmk.fp @@ -0,0 +1,138 @@ +locals { + compute_unattached_disks_not_encrypted_with_cmk_query = <<-EOQ + select + concat(id, ' [', subscription_id, '/', resource_group, ']') as title, + name, + resource_group, + subscription_id, + _ctx ->> 'connection_name' as conn + from + azure_compute_disk + where + disk_state != 'Attached' + and encryption_type <> 'EncryptionAtRestWithCustomerKey'; + EOQ +} + +variable "compute_unattached_disks_not_encrypted_with_cmk_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/Compute" + } +} + +variable "compute_unattached_disks_not_encrypted_with_cmk_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/Compute" + } +} + +trigger "query" "detect_and_correct_compute_unattached_disks_not_encrypted_with_cmk" { + title = "Detect & correct unattached Compute disks not encrypted with CMK" + description = "Detect unattached Compute disks not encrypted with CMK then encrypt with CMK." + tags = local.compute_common_tags + + enabled = var.compute_unattached_disks_not_encrypted_with_cmk_trigger_enabled + schedule = var.compute_unattached_disks_not_encrypted_with_cmk_trigger_schedule + database = var.database + sql = local.compute_unattached_disks_not_encrypted_with_cmk_query + + capture "insert" { + pipeline = pipeline.correct_compute_unattached_disks_not_encrypted_with_cmk + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_compute_unattached_disks_not_encrypted_with_cmk" { + title = "Detect & correct unattached Compute disks not encrypted with CMK" + description = "Detect unattached Compute disks not encrypted with CMK then encrypt with CMK." + tags = local.compute_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.compute_unattached_disks_not_encrypted_with_cmk_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_compute_unattached_disks_not_encrypted_with_cmk + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_compute_unattached_disks_not_encrypted_with_cmk" { + title = "Correct unattached Compute disks not encrypted with CMK" + description = "Encrypt unattached Compute disks with CMK for disks not encrypted with CMK." + tags = merge(local.compute_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + name = string + resource_group = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} Compute disk(s) not encrypted with CMK." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected Compute disk ${each.value.title} not encrypted with CMK." + } +} + + + diff --git a/pipelines/compute/compute_vms_without_managed_disk.fp b/pipelines/compute/compute_vms_without_managed_disk.fp new file mode 100644 index 0000000..6f4ac0d --- /dev/null +++ b/pipelines/compute/compute_vms_without_managed_disk.fp @@ -0,0 +1,130 @@ +locals { + compute_vms_without_managed_disk_query = <<-EOQ + select + concat(id, ' [', subscription_id, '/', resource_group, ']') as title, + vm_id as id, + subscription_id, + _ctx ->> 'connection_name' as conn + from + azure_compute_virtual_machine + where + managed_disk_id is null; + EOQ +} + +variable "compute_vms_without_managed_disk_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/Compute" + } +} + +variable "compute_vms_without_managed_disk_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/Compute" + } +} + +trigger "query" "detect_and_correct_compute_vms_without_managed_disk" { + title = "Detect & correct Compute VMs without a managed disk" + description = "Detect Compute VMs without a managed disk." + tags = local.compute_common_tags + + enabled = var.compute_vms_without_managed_disk_trigger_enabled + schedule = var.compute_vms_without_managed_disk_trigger_schedule + database = var.database + sql = local.compute_vms_without_managed_disk_query + + capture "insert" { + pipeline = pipeline.correct_compute_vms_without_managed_disk + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_compute_vms_without_managed_disk" { + title = "Detect & correct Compute VMs without a managed disk" + description = "Detect Compute VMs without a managed disk." + tags = local.compute_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.compute_vms_without_managed_disk_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_compute_vms_without_managed_disk + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_compute_vms_without_managed_disk" { + title = "Correct Compute VMs without a managed diskk" + description = "Send notifications for Compute VMs without a managed disk." + tags = merge(local.compute_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} Compute VM(s) without managed disk." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected Compute VM ${each.value.title} without managed disk." + } +} diff --git a/pipelines/cosmosdb/cosmosdb.fp b/pipelines/cosmosdb/cosmosdb.fp new file mode 100644 index 0000000..45ded09 --- /dev/null +++ b/pipelines/cosmosdb/cosmosdb.fp @@ -0,0 +1,5 @@ +locals { + cosmosdb_common_tags = merge(local.azure_compliance_common_tags, { + service = "Azure/CosmosDB" + }) +} \ No newline at end of file diff --git a/pipelines/cosmosdb/cosmosdb_accounts_with_virtual_network_filter_disabled.fp b/pipelines/cosmosdb/cosmosdb_accounts_with_virtual_network_filter_disabled.fp new file mode 100644 index 0000000..b2a6f33 --- /dev/null +++ b/pipelines/cosmosdb/cosmosdb_accounts_with_virtual_network_filter_disabled.fp @@ -0,0 +1,130 @@ +locals { + cosmosdb_accounts_with_virtual_network_filter_disabled_query = <<-EOQ + select + concat(a.id, ' [', a.subscription_id, '/', a.resource_group, ']') as title, + a.id as id, + subscription_id, + _ctx ->> 'connection_name' as conn + from + azure_cosmosdb_account as a + where + public_network_access = 'Enabled' and is_virtual_network_filter_enabled = 'false'; + EOQ +} + +variable "cosmosdb_accounts_with_virtual_network_filter_disabled_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/CosmosDB" + } +} + +variable "cosmosdb_accounts_with_virtual_network_filter_disabled_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/CosmosDB" + } +} + +trigger "query" "detect_and_correct_cosmosdb_accounts_with_virtual_network_filter_disabled" { + title = "Detect & correct Cosmos DB accounts with virtual network filter disabled" + description = "Detect Cosmos DB accounts with virtual network filter disabled." + tags = local.cosmosdb_common_tags + + enabled = var.cosmosdb_accounts_with_virtual_network_filter_disabled_trigger_enabled + schedule = var.cosmosdb_accounts_with_virtual_network_filter_disabled_trigger_schedule + database = var.database + sql = local.cosmosdb_accounts_with_virtual_network_filter_disabled_query + + capture "insert" { + pipeline = pipeline.correct_cosmosdb_accounts_with_virtual_network_filter_disabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_cosmosdb_accounts_with_virtual_network_filter_disabled" { + title = "Detect & correct Cosmos DB accounts with virtual network filter disabled" + description = "Detect Cosmos DB accounts with virtual network filter disabled." + tags = local.cosmosdb_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.cosmosdb_accounts_with_virtual_network_filter_disabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_cosmosdb_accounts_with_virtual_network_filter_disabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_cosmosdb_accounts_with_virtual_network_filter_disabled" { + title = "Correct Cosmos DB accounts with virtual network filter disabled" + description = "Send notifications for Cosmos DB accounts with virtual network filter disabled." + tags = merge(local.cosmosdb_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} Cosmos DB account(s) with virtual network filter disabled." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected Cosmos DB account ${each.value.title} with virtual network filter disabled." + } +} diff --git a/pipelines/cosmosdb/cosmosdb_accounts_without_private_link.fp b/pipelines/cosmosdb/cosmosdb_accounts_without_private_link.fp new file mode 100644 index 0000000..c0d7bd2 --- /dev/null +++ b/pipelines/cosmosdb/cosmosdb_accounts_without_private_link.fp @@ -0,0 +1,140 @@ +locals { + cosmosdb_accounts_without_private_link_query = <<-EOQ + with cosmosdb_private_connection as ( + select + distinct a.id + from + azure_cosmosdb_account as a, + jsonb_array_elements(private_endpoint_connections) as connection + where + connection -> 'properties' -> 'privateLinkServiceConnectionState' ->> 'status' = 'Approved' + ) + select + concat(a.id, ' [', a.subscription_id, '/', a.resource_group, ']') as title, + a.id as id, + subscription_id, + _ctx ->> 'connection_name' as conn + from + azure_cosmosdb_account as a + left join cosmosdb_private_connection as c on c.id = a.id + where + c.id is null; + EOQ +} + +variable "cosmosdb_accounts_without_private_link_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/CosmosDB" + } +} + +variable "cosmosdb_accounts_without_private_link_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/CosmosDB" + } +} + +trigger "query" "detect_and_correct_cosmosdb_accounts_without_private_link" { + title = "Detect & correct Cosmos DB accounts without a private link" + description = "Detect Cosmos DB accounts without a private link." + tags = local.cosmosdb_common_tags + + enabled = var.cosmosdb_accounts_without_private_link_trigger_enabled + schedule = var.cosmosdb_accounts_without_private_link_trigger_schedule + database = var.database + sql = local.cosmosdb_accounts_without_private_link_query + + capture "insert" { + pipeline = pipeline.correct_cosmosdb_accounts_without_private_link + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_cosmosdb_accounts_without_private_link" { + title = "Detect & correct Cosmos DB accounts without a private link" + description = "Detect Cosmos DB accounts without a private link." + tags = local.cosmosdb_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.cosmosdb_accounts_without_private_link_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_cosmosdb_accounts_without_private_link + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_cosmosdb_accounts_without_private_link" { + title = "Correct Cosmos DB accounts without a private link" + description = "Send notifications for Cosmos DB accounts without a private link." + tags = merge(local.cosmosdb_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} Cosmos DB account(s) without a private link." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected Cosmos DB account ${each.value.title} without a private link." + } +} diff --git a/pipelines/iam/iam.fp b/pipelines/iam/iam.fp new file mode 100644 index 0000000..5a91496 --- /dev/null +++ b/pipelines/iam/iam.fp @@ -0,0 +1,5 @@ +locals { + iam_common_tags = merge(local.azure_compliance_common_tags, { + service = "Azure/IAM" + }) +} \ No newline at end of file diff --git a/pipelines/iam/iam_authorization_policies_allowing_to_create_security_group.fp b/pipelines/iam/iam_authorization_policies_allowing_to_create_security_group.fp new file mode 100644 index 0000000..cbf1fb8 --- /dev/null +++ b/pipelines/iam/iam_authorization_policies_allowing_to_create_security_group.fp @@ -0,0 +1,138 @@ +locals { + iam_authorization_policies_allowing_to_create_security_group_query = <<-EOQ + with distinct_tenant as ( + select + distinct tenant_id, + subscription_id, + _ctx + from + azure_tenant + ) + select + concat(a.id, ' [', t.tenant_id, ']') as title, + t.tenant_id , + a._ctx ->> 'connection_name' as conn + from + distinct_tenant as t, + azuread_authorization_policy as a + where + a.default_user_role_permissions ->> 'allowedToCreateSecurityGroups' = 'true'; + EOQ +} + +variable "iam_authorization_policies_allowing_to_create_security_group_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/IAM" + } +} + +variable "iam_authorization_policies_allowing_to_create_security_group_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/IAM" + } +} + +trigger "query" "detect_and_correct_iam_authorization_policies_allowing_to_create_security_group" { + title = "Detect & correct Authorization policies allowing IAM users to create security group" + description = "Detect authorization policies allowing IAM users to create security group." + tags = local.iam_common_tags + + enabled = var.iam_authorization_policies_allowing_to_create_security_group_trigger_enabled + schedule = var.iam_authorization_policies_allowing_to_create_security_group_trigger_schedule + database = var.database + sql = local.iam_authorization_policies_allowing_to_create_security_group_query + + capture "insert" { + pipeline = pipeline.correct_iam_authorization_policies_allowing_to_create_security_group + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_iam_authorization_policies_allowing_to_create_security_group" { + title = "Detect & correct authorization policies allowing IAM users to create security group" + description = "Detect authorization policies allowing IAM users to create security group." + tags = local.iam_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.iam_authorization_policies_allowing_to_create_security_group_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_iam_authorization_policies_allowing_to_create_security_group + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_iam_authorization_policies_allowing_to_create_security_group" { + title = "Correct Authorization policies allowing IAM users to create security group" + description = "Send notifications for authorization policies allowing IAM users to create security group." + tags = merge(local.iam_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} authorization policies allowing users to create security group." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected authorization policy ${each.value.title} allowing users to create security group." + } +} diff --git a/pipelines/iam/iam_authorization_policies_allowing_to_register_application.fp b/pipelines/iam/iam_authorization_policies_allowing_to_register_application.fp new file mode 100644 index 0000000..7e1b405 --- /dev/null +++ b/pipelines/iam/iam_authorization_policies_allowing_to_register_application.fp @@ -0,0 +1,137 @@ +locals { + iam_authorization_policies_allowing_to_register_application_query = <<-EOQ + with distinct_tenant as ( + select + distinct tenant_id, + _ctx + from + azure_tenant + ) + select + concat(a.id, ' [', t.tenant_id, ']') as title, + t.tenant_id , + a._ctx ->> 'connection_name' as conn + from + distinct_tenant as t, + azuread_authorization_policy as a + where + a.default_user_role_permissions ->> 'allowedToCreateApps' = 'true'; + EOQ +} + +variable "iam_authorization_policies_allowing_to_register_application_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/IAM" + } +} + +variable "iam_authorization_policies_allowing_to_register_application_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/IAM" + } +} + +trigger "query" "detect_and_correct_iam_authorization_policies_allowing_to_register_application" { + title = "Detect & correct authorization policy allowing IAM users to register application" + description = "Detect authorization policy allowing IAM users to register application." + tags = local.iam_common_tags + + enabled = var.iam_authorization_policies_allowing_to_register_application_trigger_enabled + schedule = var.iam_authorization_policies_allowing_to_register_application_trigger_schedule + database = var.database + sql = local.iam_authorization_policies_allowing_to_register_application_query + + capture "insert" { + pipeline = pipeline.correct_iam_authorization_policies_allowing_to_register_application + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_iam_authorization_policies_allowing_to_register_application" { + title = "Detect & correct authorization policy allowing IAM users to register application" + description = "Detect authorization policy allowing IAM users to register application." + tags = local.iam_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.iam_authorization_policies_allowing_to_register_application_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_iam_authorization_policies_allowing_to_register_application + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_iam_authorization_policies_allowing_to_register_application" { + title = "Correct authorization policy allowing IAM users to register application" + description = "Send notifications for authorization policy allowing IAM users to register application." + tags = merge(local.iam_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} authorization policy allowing users to register application." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected authorization policy ${each.value.title} allowing users to register application." + } +} diff --git a/pipelines/iam/iam_conditional_access_with_mfa_disabled.fp b/pipelines/iam/iam_conditional_access_with_mfa_disabled.fp new file mode 100644 index 0000000..d3f2d3e --- /dev/null +++ b/pipelines/iam/iam_conditional_access_with_mfa_disabled.fp @@ -0,0 +1,136 @@ +locals { + iam_conditional_access_with_mfa_disabled_query = <<-EOQ + with distinct_tenant as ( + select + distinct tenant_id + from + azure_tenant + ) + select + concat(p.id, ' [', t.tenant_id, ']') as title, + t.tenant_id, + _ctx ->> 'connection_name' as conn + from + distinct_tenant as t, + azuread_conditional_access_policy as p + where + not p.built_in_controls @> '["mfa"]'; + EOQ +} + +variable "iam_conditional_access_with_mfa_disabled_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/IAM" + } +} + +variable "iam_conditional_access_with_mfa_disabled_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/IAM" + } +} + +trigger "query" "detect_and_correct_iam_conditional_access_with_mfa_disabled" { + title = "Detect & correct IAM conditional access with MFA disabled" + description = "Detect IAM conditional access with MFA disabled." + tags = local.iam_common_tags + + enabled = var.iam_conditional_access_with_mfa_disabled_trigger_enabled + schedule = var.iam_conditional_access_with_mfa_disabled_trigger_schedule + database = var.database + sql = local.iam_conditional_access_with_mfa_disabled_query + + capture "insert" { + pipeline = pipeline.correct_iam_conditional_access_with_mfa_disabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_iam_conditional_access_with_mfa_disabled" { + title = "Detect & correct IAM conditional access with MFA disabled" + description = "Detect IAM conditional access with MFA disabled." + tags = local.iam_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.iam_conditional_access_with_mfa_disabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_iam_conditional_access_with_mfa_disabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_iam_conditional_access_with_mfa_disabled" { + title = "Correct IAM conditional access with MFA disabled" + description = "Send notifications for IAM conditional access with MFA disabled." + tags = merge(local.iam_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} conditional access with MFA disabled." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected conditional access ${each.value.title} with MFA disabled." + } +} diff --git a/pipelines/iam/iam_conditional_access_with_mfa_disabled_for_administrators.fp b/pipelines/iam/iam_conditional_access_with_mfa_disabled_for_administrators.fp new file mode 100644 index 0000000..5388dce --- /dev/null +++ b/pipelines/iam/iam_conditional_access_with_mfa_disabled_for_administrators.fp @@ -0,0 +1,141 @@ +locals { + iam_conditional_access_with_mfa_disabled_for_administrators_query = <<-EOQ + with distinct_tenant as ( + select + distinct u.id, + tenant_id + from + azuread_user as u + left join azure_role_assignment as a on a.principal_id = u.id + left join azure_role_definition as d on d.id = a.role_definition_id + where + role_type = 'BuiltInRole' and (role_name like '%Administrator%' or role_name = 'Owner') + ) + select + concat(p.id, ' [', t.tenant_id, ']') as title, + t.tenant_id, + _ctx ->> 'connection_name' as conn + from + distinct_tenant as t, + azuread_conditional_access_policy as p + where + not p.built_in_controls @> '["mfa"]'; + EOQ +} + +variable "iam_conditional_access_with_mfa_disabled_for_administrators_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/IAM" + } +} + +variable "iam_conditional_access_with_mfa_disabled_for_administrators_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/IAM" + } +} + +trigger "query" "detect_and_correct_iam_conditional_access_with_mfa_disabled_for_administrators" { + title = "Detect & correct IAM conditional access with MFA disabled for administrators" + description = "Detect IAM conditional access with MFA disabled for administrators." + tags = local.iam_common_tags + + enabled = var.iam_conditional_access_with_mfa_disabled_for_administrators_trigger_enabled + schedule = var.iam_conditional_access_with_mfa_disabled_for_administrators_trigger_schedule + database = var.database + sql = local.iam_conditional_access_with_mfa_disabled_for_administrators_query + + capture "insert" { + pipeline = pipeline.correct_iam_conditional_access_with_mfa_disabled_for_administrators + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_iam_conditional_access_with_mfa_disabled_for_administrators" { + title = "Detect & correct IAM conditional access with MFA disabled for administrators" + description = "Detect IAM conditional access with MFA disabled for administrators." + tags = local.iam_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.iam_conditional_access_with_mfa_disabled_for_administrators_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_iam_conditional_access_with_mfa_disabled_for_administrators + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_iam_conditional_access_with_mfa_disabled_for_administrators" { + title = "Correct IAM conditional access with MFA disabled for administrators" + description = "Send notifications for IAM conditional access with MFA disabled for administrators." + tags = merge(local.iam_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} IAM conditional access with MFA disabled for administrators." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected conditional access ${each.value.title} with MFA disabled for administrators." + } +} diff --git a/pipelines/iam/iam_subscriptions_with_custom_owner_roles.fp b/pipelines/iam/iam_subscriptions_with_custom_owner_roles.fp new file mode 100644 index 0000000..c53ae2c --- /dev/null +++ b/pipelines/iam/iam_subscriptions_with_custom_owner_roles.fp @@ -0,0 +1,312 @@ +locals { + iam_subscriptions_with_custom_owner_roles_query = <<-EOQ + select + concat(id, ' [', subscription_id, '/', role_name, ']') as title, + id as id, + role_name as name, + subscription_id, + _ctx ->> 'connection_name' as conn + from + azure_role_definition, + jsonb_array_elements(permissions) as s, + jsonb_array_elements_text(s -> 'actions') as action + where + role_type = 'CustomRole' + and action in ('*', '*:*'); + EOQ + + iam_subscriptions_with_custom_owner_roles_enabled_actions_enum = ["skip", "delete_role"] + iam_subscriptions_with_custom_owner_roles_default_action_enum = ["notify", "skip", "delete_role"] +} + +variable "iam_subscriptions_with_custom_owner_roles_trigger_enabled" { + type = bool + default = false + description = "If true, the trigger is enabled." + + tags = { + folder = "Advanced/IAM" + } +} + +variable "iam_subscriptions_with_custom_owner_roles_trigger_schedule" { + type = string + default = "15m" + description = "If the trigger is enabled, run it on this schedule." + + tags = { + folder = "Advanced/IAM" + } +} + +variable "iam_subscriptions_with_custom_owner_roles_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/IAM" + } +} + +variable "iam_subscriptions_with_custom_owner_roles_enabled_actions" { + type = list(string) + description = "The list of enabled actions to provide to approvers for selection." + default = ["skip", "delete_role"] + + tags = { + folder = "Advanced/IAM" + } +} + +trigger "query" "detect_and_correct_iam_subscriptions_with_custom_owner_roles" { + title = "Detect & correct subscriptions with custom owner roles" + description = "Detect subscriptions with custom owner roles and then delete custom subscriptions owner roles." + tags = local.iam_common_tags + + enabled = var.iam_subscriptions_with_custom_owner_roles_trigger_enabled + schedule = var.iam_subscriptions_with_custom_owner_roles_trigger_schedule + database = var.database + sql = local.iam_subscriptions_with_custom_owner_roles_query + + capture "insert" { + pipeline = pipeline.correct_iam_subscriptions_with_custom_owner_roles + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_iam_subscriptions_with_custom_owner_roles" { + title = "Detect & correct subscriptions with custom owner roles" + description = "Detect subscriptions with custom owner roles and then delete custom subscriptions owner roles." + tags = merge(local.iam_common_tags, { recommended = "true" }) + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.iam_subscriptions_with_custom_owner_roles_default_action + enum = local.iam_subscriptions_with_custom_owner_roles_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.iam_subscriptions_with_custom_owner_roles_enabled_actions + enum = local.iam_subscriptions_with_custom_owner_roles_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.iam_subscriptions_with_custom_owner_roles_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_iam_subscriptions_with_custom_owner_roles + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_iam_subscriptions_with_custom_owner_roles" { + title = "Correct subscriptions with custom owner roles" + description = "Runs corrective action on a collection of subscriptions with custom owner roles." + tags = merge(local.iam_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + name = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.iam_subscriptions_with_custom_owner_roles_default_action + enum = local.iam_subscriptions_with_custom_owner_roles_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.iam_subscriptions_with_custom_owner_roles_enabled_actions + enum = local.iam_subscriptions_with_custom_owner_roles_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} subscription(s) with custom owner roles." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.id => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_iam_custom_subscription_owner_role_existing + args = { + title = each.value.title + name = each.value.name + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_iam_custom_subscription_owner_role_existing" { + title = "Correct one Subscription with custom owner roles" + description = "Runs corrective action on a single subscription with custom owner roles." + tags = merge(local.iam_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "name" { + type = string + description = "The name of the custom subscription owner role." + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.iam_subscriptions_with_custom_owner_roles_default_action + enum = local.iam_subscriptions_with_custom_owner_roles_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.iam_subscriptions_with_custom_owner_roles_enabled_actions + enum = local.iam_subscriptions_with_custom_owner_roles_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected subscription custom owner role ${param.title}." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped subscription custom owner role ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "delete_role" = { + label = "Delete custom role" + value = "delete_role" + style = local.style_alert + pipeline_ref = azure.pipeline.delete_iam_role + pipeline_args = { + role_name = param.name + subscription_id = param.subscription_id + conn = param.conn + } + success_msg = "Deleted subscriptions with custom owner role ${param.title}." + error_msg = "Error deleting subscription with custom owner role ${param.title}." + } + } + } + } +} diff --git a/pipelines/iam/tenants_with_guest_users.fp b/pipelines/iam/tenants_with_guest_users.fp new file mode 100644 index 0000000..7c8625f --- /dev/null +++ b/pipelines/iam/tenants_with_guest_users.fp @@ -0,0 +1,141 @@ +locals { + tenants_with_guest_users_query = <<-EOQ + with distinct_tenant as ( + select + distinct tenant_id, + subscription_id, + _ctx + from + azure_tenant + ) + select + concat(display_name, ' [', u.tenant_id, ']') as title, + u.tenant_id, + u.user_principal_name as user_principal_name, + account_enabled, + extract(day from current_timestamp - u.created_date_time::timestamp), + u._ctx ->> 'connection_name' as conn + from + azuread_user as u + left join distinct_tenant as t on t.tenant_id = u.tenant_id + where + u.user_type = 'Guest'; + EOQ +} + +variable "tenants_with_guest_users_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/IAM" + } +} + +variable "tenants_with_guest_users_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/IAM" + } +} + +trigger "query" "detect_and_correct_tenants_with_guest_users" { + title = "Detect & correct Tenants with guest users" + description = "Detect tenants with guest users." + tags = local.iam_common_tags + + enabled = var.tenants_with_guest_users_trigger_enabled + schedule = var.tenants_with_guest_users_trigger_schedule + database = var.database + sql = local.tenants_with_guest_users_query + + capture "insert" { + pipeline = pipeline.correct_tenants_with_guest_users + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_tenants_with_guest_users" { + title = "Detect & correct Tenants with guest users" + description = "Detect tenants with guest users." + tags = local.iam_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.tenants_with_guest_users_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_tenants_with_guest_users + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_tenants_with_guest_users" { + title = "Correct Tenants with guest users" + description = "Send notifications for tenants with guest users." + tags = merge(local.iam_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} guest user(s)." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected guest user ${each.value.title}." + } +} diff --git a/pipelines/iam/tenants_with_more_than_five_iam_global_administrator.fp b/pipelines/iam/tenants_with_more_than_five_iam_global_administrator.fp new file mode 100644 index 0000000..07f8c9b --- /dev/null +++ b/pipelines/iam/tenants_with_more_than_five_iam_global_administrator.fp @@ -0,0 +1,138 @@ +locals { + tenants_with_more_than_five_iam_global_administrator_query = <<-EOQ + with distinct_tenant as ( + select + distinct tenant_id + from + azure_tenant + ) + select + t.tenant_id as title, + (jsonb_array_length(member_ids))::text as global_administrator_count, + _ctx ->> 'connection_name' as conn + from + distinct_tenant as t, + azuread_directory_role as p + where + display_name = 'Global Administrator' + and jsonb_array_length(member_ids) > 5 + EOQ +} + +variable "tenants_with_more_than_five_iam_global_administrator_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/IAM" + } +} + +variable "tenants_with_more_than_five_iam_global_administrator_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/IAM" + } +} + +trigger "query" "detect_and_correct_tenants_with_more_than_five_iam_global_administrator" { + title = "Detect & correct Tenants with more than five IAM global administrator" + description = "Detect tenants with more than five IAM global administrator." + tags = local.iam_common_tags + + enabled = var.tenants_with_more_than_five_iam_global_administrator_trigger_enabled + schedule = var.tenants_with_more_than_five_iam_global_administrator_trigger_schedule + database = var.database + sql = local.tenants_with_more_than_five_iam_global_administrator_query + + capture "insert" { + pipeline = pipeline.correct_tenants_with_more_than_five_iam_global_administrator + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_tenants_with_more_than_five_iam_global_administrator" { + title = "Detect & correct Tenants with more than five IAM global administrator" + description = "Detect tenants with more than five IAM global administrator." + tags = local.iam_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.tenants_with_more_than_five_iam_global_administrator_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_tenants_with_more_than_five_iam_global_administrator + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_tenants_with_more_than_five_iam_global_administrator" { + title = "Correct Tenants with more than five IAM global administrator" + description = "Send notifications for tenants with more than five IAM global administrator." + tags = merge(local.iam_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + global_administrator_count = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} tenant(s) with more than five IAM global administrator." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected tenant ${each.value.title} with ${each.value.global_administrator_count} IAM global administrator." + } +} diff --git a/pipelines/keyvault/keyvault.fp b/pipelines/keyvault/keyvault.fp new file mode 100644 index 0000000..4c0399c --- /dev/null +++ b/pipelines/keyvault/keyvault.fp @@ -0,0 +1,5 @@ +locals { + keyvault_common_tags = merge(local.azure_compliance_common_tags, { + service = "Azure/KeyVault" + }) +} \ No newline at end of file diff --git a/pipelines/keyvault/keyvault_vaults_with_logging_disabled.fp b/pipelines/keyvault/keyvault_vaults_with_logging_disabled.fp new file mode 100644 index 0000000..2db9286 --- /dev/null +++ b/pipelines/keyvault/keyvault_vaults_with_logging_disabled.fp @@ -0,0 +1,148 @@ +locals { + keyvault_vaults_with_logging_disabled_query = <<-EOQ + with logging_details as ( + select + name as key_vault_name + from + azure_key_vault, + jsonb_array_elements(diagnostic_settings) setting, + jsonb_array_elements(setting -> 'properties' -> 'logs') log + where + diagnostic_settings is not null + and setting -> 'properties' ->> 'storageAccountId' <> '' + and (log ->> 'enabled') :: boolean + and log ->> 'category' = 'AuditEvent' + and (log -> 'retentionPolicy') :: JSONB ? 'days' + ) + select + concat(v.id, ' [', v.subscription_id, '/', v.resource_group, ']') as title, + v.id as id, + v.name, + v.resource_group, + v.subscription_id, + v._ctx ->> 'connection_name' as conn + from + azure_key_vault v + left join logging_details l on l.key_vault_name = v.name + where + v.diagnostic_settings is null + or l.key_vault_name not like concat('%', v.name, '%'); + EOQ +} + +variable "keyvault_vaults_with_logging_disabled_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/KeyVault" + } +} + +variable "keyvault_vaults_with_logging_disabled_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/KeyVault" + } +} + +trigger "query" "detect_and_correct_keyvault_vaults_with_logging_disabled" { + title = "Detect & correct Key Vaults with logging disabled" + description = "Detect key vaults with logging disabled." + tags = local.keyvault_common_tags + + enabled = var.keyvault_vaults_with_logging_disabled_trigger_enabled + schedule = var.keyvault_vaults_with_logging_disabled_trigger_schedule + database = var.database + sql = local.keyvault_vaults_with_logging_disabled_query + + capture "insert" { + pipeline = pipeline.correct_keyvault_vaults_with_logging_disabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_keyvault_vaults_with_logging_disabled" { + title = "Detect & correct Key Vaults with logging disabled" + description = "Detect key vaults with logging disabled." + tags = local.keyvault_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.keyvault_vaults_with_logging_disabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_keyvault_vaults_with_logging_disabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_keyvault_vaults_with_logging_disabled" { + title = "Correct Key Vaults with logging disabled" + description = "Send notifications for key vaults with logging disabled." + tags = merge(local.keyvault_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} key vault(s) with logging disabled." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected key vault ${each.value.title} with logging disabled." + } +} diff --git a/pipelines/keyvault/keyvault_vaults_with_purge_protection_disabled.fp b/pipelines/keyvault/keyvault_vaults_with_purge_protection_disabled.fp new file mode 100644 index 0000000..99b141d --- /dev/null +++ b/pipelines/keyvault/keyvault_vaults_with_purge_protection_disabled.fp @@ -0,0 +1,319 @@ +locals { + keyvault_vaults_with_purge_protection_disabled_query = <<-EOQ + select + concat(vault.id, ' [', vault.subscription_id, '/', vault.resource_group, ']') as title, + vault.id as id, + vault.name, + vault.resource_group, + vault.subscription_id, + vault._ctx ->> 'connection_name' as conn + from + azure_key_vault as vault + where + not (soft_delete_enabled and purge_protection_enabled); + EOQ + + keyvault_vaults_with_purge_protection_disabled_enabled_actions_enum = ["skip", "enable_purge_protection"] + keyvault_vaults_with_purge_protection_disabled_default_action_enum = ["notify", "skip", "enable_purge_protection"] +} + +variable "keyvault_vaults_with_purge_protection_disabled_trigger_enabled" { + type = bool + default = false + description = "If true, the trigger is enabled." + + tags = { + folder = "Advanced/KeyVault" + } +} + +variable "keyvault_vaults_with_purge_protection_disabled_trigger_schedule" { + type = string + default = "15m" + description = "If the trigger is enabled, run it on this schedule." + + tags = { + folder = "Advanced/KeyVault" + } +} + +variable "keyvault_vaults_with_purge_protection_disabled_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/KeyVault" + } +} + +variable "keyvault_vaults_with_purge_protection_disabled_enabled_actions" { + type = list(string) + description = "The list of enabled actions to provide to approvers for selection." + default = ["skip", "enable_purge_protection"] + + tags = { + folder = "Advanced/KeyVault" + } +} + +trigger "query" "detect_and_correct_keyvault_vaults_with_purge_protection_disabled" { + title = "Detect & correct Key Vaults with purge protection disabled" + description = "Detect key vaults with purge protection disabled and then enable purge protection." + tags = local.keyvault_common_tags + + enabled = var.keyvault_vaults_with_purge_protection_disabled_trigger_enabled + schedule = var.keyvault_vaults_with_purge_protection_disabled_trigger_schedule + database = var.database + sql = local.keyvault_vaults_with_purge_protection_disabled_query + + capture "insert" { + pipeline = pipeline.correct_keyvault_vaults_with_purge_protection_disabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_keyvault_vaults_with_purge_protection_disabled" { + title = "Detect & correct Key Vaults with purge protection disabled" + description = "Detect key vaults with purge protection disabled and then enable purge protection." + tags = merge(local.keyvault_common_tags, { recommended = "true" }) + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.keyvault_vaults_with_purge_protection_disabled_default_action + enum = local.keyvault_vaults_with_purge_protection_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.keyvault_vaults_with_purge_protection_disabled_enabled_actions + enum = local.keyvault_vaults_with_purge_protection_disabled_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.keyvault_vaults_with_purge_protection_disabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_keyvault_vaults_with_purge_protection_disabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_keyvault_vaults_with_purge_protection_disabled" { + title = "Correct Key Vaults with purge protection disabled" + description = "Enable purge protection on a collection of key vaults with purge protection disabled." + tags = merge(local.keyvault_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + name = string + resource_group = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.keyvault_vaults_with_purge_protection_disabled_default_action + enum = local.keyvault_vaults_with_purge_protection_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.keyvault_vaults_with_purge_protection_disabled_enabled_actions + enum = local.keyvault_vaults_with_purge_protection_disabled_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} non-recoverable Key Vaults." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.id => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_keyvault_vault_non_recoverable + args = { + title = each.value.title + name = each.value.name + resource_group = each.value.resource_group + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_keyvault_vault_non_recoverable" { + title = "Correct one Key Vault with purge protection disabled" + description = "Enable purge protection on a key vault with purge protection disabled." + tags = merge(local.keyvault_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "name" { + type = string + description = "The name of the Key Vault." + } + + param "resource_group" { + type = string + description = local.description_resource_group + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.keyvault_vaults_with_purge_protection_disabled_default_action + enum = local.keyvault_vaults_with_purge_protection_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.keyvault_vaults_with_purge_protection_disabled_enabled_actions + enum = local.keyvault_vaults_with_purge_protection_disabled_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected key vault ${param.title} as non-recoverable." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped key vault ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "enable_purge_protection" = { + label = "Enable purge protection" + value = "enable_purge_protection" + style = local.style_alert + pipeline_ref = azure.pipeline.update_azure_key_vault_purge_protection + pipeline_args = { + resource_group = param.resource_group + subscription_id = param.subscription_id + vault_name = param.name + conn = param.conn + enable_purge_protection = true + } + success_msg = "Enabled purge protection for key vault ${param.title}." + error_msg = "Error enabling purge protection for key vault ${param.title}." + } + } + } + } +} diff --git a/pipelines/keyvault/keyvault_vaults_with_rbac_disabled.fp b/pipelines/keyvault/keyvault_vaults_with_rbac_disabled.fp new file mode 100644 index 0000000..154f6cc --- /dev/null +++ b/pipelines/keyvault/keyvault_vaults_with_rbac_disabled.fp @@ -0,0 +1,319 @@ +locals { + keyvault_vaults_with_rbac_disabled_query = <<-EOQ + select + concat(v.id, ' [', v.subscription_id, '/', v.resource_group, ']') as title, + v.id as id, + v.name, + v.resource_group, + v.subscription_id, + v._ctx ->> 'connection_name' as conn + from + azure_key_vault as v + where + not enable_rbac_authorization; + EOQ + + keyvault_vaults_with_rbac_disabled_enabled_actions_enum = ["skip", "enable_rbac"] + keyvault_vaults_with_rbac_disabled_default_action_enum = ["notify", "skip", "enable_rbac"] +} + +variable "keyvault_vaults_with_rbac_disabled_trigger_enabled" { + type = bool + default = false + description = "If true, the trigger is enabled." + + tags = { + folder = "Advanced/KeyVault" + } +} + +variable "keyvault_vaults_with_rbac_disabled_trigger_schedule" { + type = string + default = "15m" + description = "If the trigger is enabled, run it on this schedule." + + tags = { + folder = "Advanced/KeyVault" + } +} + +variable "keyvault_vaults_with_rbac_disabled_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/KeyVault" + } +} + +variable "keyvault_vaults_with_rbac_disabled_enabled_actions" { + type = list(string) + description = "The list of enabled actions to provide to approvers for selection." + default = ["skip", "enable_rbac"] + + tags = { + folder = "Advanced/KeyVault" + } +} + +trigger "query" "detect_and_correct_keyvault_vaults_with_rbac_disabled" { + title = "Detect & correct Key Vaults with RBAC disabled" + description = "Detect Key Vaults with RBAC disabled." + tags = local.keyvault_common_tags + + enabled = var.keyvault_vaults_with_rbac_disabled_trigger_enabled + schedule = var.keyvault_vaults_with_rbac_disabled_trigger_schedule + database = var.database + sql = local.keyvault_vaults_with_rbac_disabled_query + + capture "insert" { + pipeline = pipeline.correct_keyvault_vaults_with_rbac_disabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_keyvault_vaults_with_rbac_disabled" { + title = "Detect & correct Key Vaults with RBAC disabled" + description = "Detect Key Vaults with RBAC disabled." + tags = merge(local.keyvault_common_tags, { recommended = "true" }) + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.keyvault_vaults_with_rbac_disabled_default_action + enum = local.keyvault_vaults_with_rbac_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.keyvault_vaults_with_rbac_disabled_enabled_actions + enum = local.keyvault_vaults_with_rbac_disabled_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.keyvault_vaults_with_rbac_disabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_keyvault_vaults_with_rbac_disabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_keyvault_vaults_with_rbac_disabled" { + title = "Correct Key Vaults with RBAC disabled" + description = "Enable RBAC on a collection of RBAC disabled Key Vaults." + tags = merge(local.keyvault_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + name = string + resource_group = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.keyvault_vaults_with_rbac_disabled_default_action + enum = local.keyvault_vaults_with_rbac_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.keyvault_vaults_with_rbac_disabled_enabled_actions + enum = local.keyvault_vaults_with_rbac_disabled_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} non-recoverable Key Vaults." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.id => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_keyvault_vault_with_rbac_disabled + args = { + title = each.value.title + name = each.value.name + resource_group = each.value.resource_group + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_keyvault_vault_with_rbac_disabled" { + title = "Correct one Key Vault with RBAC disabled" + description = "Enable RBAC on a single key vault with RBAC disabled." + tags = merge(local.keyvault_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "name" { + type = string + description = "The name of the Key Vault." + } + + param "resource_group" { + type = string + description = local.description_resource_group + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.keyvault_vaults_with_rbac_disabled_default_action + enum = local.keyvault_vaults_with_rbac_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.keyvault_vaults_with_rbac_disabled_enabled_actions + enum = local.keyvault_vaults_with_rbac_disabled_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected Key Vault ${param.title} with RBAC disabled." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped Key Vault ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "enable_rbac" = { + label = "Enable RBAC" + value = "enable_rbac" + style = local.style_alert + pipeline_ref = azure.pipeline.update_key_vault_rbac_authorization + pipeline_args = { + resource_group = param.resource_group + subscription_id = param.subscription_id + vault_name = param.name + conn = param.conn + enable_rbac_authorization = true + } + success_msg = "Enabled RBAC for Key Vault ${param.title}." + error_msg = "Error enabling RBAC for Key Vault ${param.title}." + } + } + } + } +} diff --git a/pipelines/keyvault/keyvault_vaults_without_private_link.fp b/pipelines/keyvault/keyvault_vaults_without_private_link.fp new file mode 100644 index 0000000..134cded --- /dev/null +++ b/pipelines/keyvault/keyvault_vaults_without_private_link.fp @@ -0,0 +1,133 @@ +locals { + keyvault_vaults_without_private_link_query = <<-EOQ + select + concat(vault.id, ' [', vault.subscription_id, '/', vault.resource_group, ']') as title, + vault.id as id, + vault.name, + vault.resource_group, + vault.subscription_id, + vault._ctx ->> 'connection_name' as conn + from + azure_key_vault as vault + where + private_endpoint_connections is null + or ( not private_endpoint_connections @> '[{"PrivateLinkServiceConnectionStateStatus": "Approved"}]'); + EOQ +} + +variable "keyvault_vaults_without_private_link_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/KeyVault" + } +} + +variable "keyvault_vaults_without_private_link_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/KeyVault" + } +} + +trigger "query" "detect_and_correct_keyvault_vaults_without_private_link" { + title = "Detect & correct Key Vaults without a private link" + description = "Detect Key Vaults without a private link." + tags = local.keyvault_common_tags + + enabled = var.keyvault_vaults_without_private_link_trigger_enabled + schedule = var.keyvault_vaults_without_private_link_trigger_schedule + database = var.database + sql = local.keyvault_vaults_without_private_link_query + + capture "insert" { + pipeline = pipeline.correct_keyvault_vaults_without_private_link + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_keyvault_vaults_without_private_link" { + title = "Detect & correct Key Vaults without a private link" + description = "Detect Key Vaults without a private link." + tags = local.keyvault_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.keyvault_vaults_without_private_link_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_keyvault_vaults_without_private_link + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_keyvault_vaults_without_private_link" { + title = "Correct Key Vaults without a private link" + description = "Send notifications for Key Vaults without a private link." + tags = merge(local.keyvault_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} Key Vault(s) without private link." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected Key Vault ${each.value.title} without private link." + } +} diff --git a/pipelines/keyvault/keyvault_with_non_rbac_keys_expiration_not_set.fp b/pipelines/keyvault/keyvault_with_non_rbac_keys_expiration_not_set.fp new file mode 100644 index 0000000..6f60f44 --- /dev/null +++ b/pipelines/keyvault/keyvault_with_non_rbac_keys_expiration_not_set.fp @@ -0,0 +1,143 @@ +locals { + keyvault_with_non_rbac_keys_expiration_not_set_query = <<-EOQ + with non_rbac_vault as ( + select + name + from + azure_key_vault + where + not enable_rbac_authorization + ) + select + concat(kvk.id, ' [', kvk.subscription_id, '/', kvk.resource_group, ']') as title, + kvk.id as id, + kvk.name, + kvk.subscription_id, + kvk.vault_name as vault_name, + kvk._ctx ->> 'connection_name' as conn + from + azure_key_vault_key kvk + left join non_rbac_vault as v on v.name = kvk.vault_name + where + enabled + and expires_at is null + and v.name is not null; + EOQ +} + +variable "keyvault_with_non_rbac_keys_expiration_not_set_trigger_enabled" { + type = bool + default = false + description = "If true, the trigger is enabled." + + tags = { + folder = "Advanced/KeyVault" + } +} + +variable "keyvault_with_non_rbac_keys_expiration_not_set_trigger_schedule" { + type = string + default = "15m" + description = "If the trigger is enabled, run it on this schedule." + + tags = { + folder = "Advanced/KeyVault" + } +} + +trigger "query" "detect_and_correct_keyvault_with_non_rbac_keys_expiration_not_set" { + title = "Detect & correct Key Vaults with non-RBAC keys without expiration date" + description = "Detect key vaults with non-RBAC keys that do not have an expiration date set and then set expiration date." + tags = local.keyvault_common_tags + + enabled = var.keyvault_with_non_rbac_keys_expiration_not_set_trigger_enabled + schedule = var.keyvault_with_non_rbac_keys_expiration_not_set_trigger_schedule + database = var.database + sql = local.keyvault_with_non_rbac_keys_expiration_not_set_query + + capture "insert" { + pipeline = pipeline.correct_keyvault_with_non_rbac_keys_expiration_not_set + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_keyvault_with_non_rbac_keys_expiration_not_set" { + title = "Detect & correct Key Vaults with non-RBAC keys without expiration date" + description = "Detect key vaults with non-RBAC keys that do not have an expiration date set and then set expiration date." + tags = local.keyvault_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.keyvault_with_non_rbac_keys_expiration_not_set_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_keyvault_with_non_rbac_keys_expiration_not_set + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_keyvault_with_non_rbac_keys_expiration_not_set" { + title = "Correct Key Vaults with non-RBAC keys without expiration date" + description = "Runs corrective action on a collection of Key Vaults with non-RBAC keys without expiration date." + tags = merge(local.keyvault_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} Key Vaults with non-RBAC keys without expiration date." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected Key Vault ${each.value.title} with non-RBAC keys without expiration date." + } +} diff --git a/pipelines/keyvault/keyvault_with_non_rbac_secrets_expiration_not_set.fp b/pipelines/keyvault/keyvault_with_non_rbac_secrets_expiration_not_set.fp new file mode 100644 index 0000000..d9c09b9 --- /dev/null +++ b/pipelines/keyvault/keyvault_with_non_rbac_secrets_expiration_not_set.fp @@ -0,0 +1,143 @@ +locals { + keyvault_with_non_rbac_secrets_expiration_not_set_query = <<-EOQ + with non_rbac_vault as ( + select + name + from + azure_key_vault + where + not enable_rbac_authorization + ) + select + concat(kvs.id, ' [', kvs.subscription_id, '/', kvs.resource_group, ']') as title, + kvs.id as id, + kvs.name, + kvs.subscription_id, + kvs.vault_name as vault_name, + kvs._ctx ->> 'connection_name' as conn + from + azure_key_vault_secret kvs + left join non_rbac_vault as v on v.name = kvs.vault_name + where + kvs.enabled and kvs.expires_at is null + and v.name is not null; + EOQ +} + +variable "keyvault_with_non_rbac_secrets_expiration_not_set_trigger_enabled" { + type = bool + default = false + description = "If true, the trigger is enabled." + + tags = { + folder = "Advanced/KeyVault" + } +} + +variable "keyvault_with_non_rbac_secrets_expiration_not_set_trigger_schedule" { + type = string + default = "15m" + description = "If the trigger is enabled, run it on this schedule." + + tags = { + folder = "Advanced/KeyVault" + } +} + +trigger "query" "detect_and_correct_keyvault_with_non_rbac_secrets_expiration_not_set" { + title = "Detect & correct Key Vaults with non-RBAC secrets without expiration date" + description = "Detect Key Vaults with non-RBAC secrets that do not have an expiration date set and then set expiration date." + tags = local.keyvault_common_tags + + enabled = var.keyvault_with_non_rbac_secrets_expiration_not_set_trigger_enabled + schedule = var.keyvault_with_non_rbac_secrets_expiration_not_set_trigger_schedule + database = var.database + sql = local.keyvault_with_non_rbac_secrets_expiration_not_set_query + + capture "insert" { + pipeline = pipeline.correct_keyvault_with_non_rbac_secrets_expiration_not_set + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_keyvault_with_non_rbac_secrets_expiration_not_set" { + title = "Detect & correct Key Vaults with non-RBAC secrets without expiration date" + description = "Detect Key Vaults with non-RBAC secrets that do not have an expiration date set and then set expiration date." + tags = local.keyvault_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.keyvault_with_non_rbac_secrets_expiration_not_set_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_keyvault_with_non_rbac_secrets_expiration_not_set + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_keyvault_with_non_rbac_secrets_expiration_not_set" { + title = "Correct Key Vaults with non-RBAC secrets without expiration date" + description = "Runs corrective action on a collection of Key Vaults with non-RBAC secrets without expiration date." + tags = merge(local.keyvault_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} Key Vaults with non-RBAC secrets without expiration date." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected Key Vault ${each.value.title} with non-RBAC secrets without expiration date." + } +} + diff --git a/pipelines/keyvault/keyvault_with_rbac_keys_expiration_not_set.fp b/pipelines/keyvault/keyvault_with_rbac_keys_expiration_not_set.fp new file mode 100644 index 0000000..6938b79 --- /dev/null +++ b/pipelines/keyvault/keyvault_with_rbac_keys_expiration_not_set.fp @@ -0,0 +1,144 @@ +locals { + keyvault_with_rbac_keys_expiration_not_set_query = <<-EOQ + with rbac_vault as ( + select + name + from + azure_key_vault + where + enable_rbac_authorization + ) + select + concat(kvk.id, ' [', kvk.subscription_id, '/', kvk.resource_group, ']') as title, + kvk.id as id, + kvk.name, + kvk.subscription_id, + kvk.vault_name as vault_name, + kvk._ctx ->> 'connection_name' as conn + from + azure_key_vault_key kvk + left join rbac_vault as v on v.name = kvk.vault_name + where + enabled + and expires_at is null + and v.name is not null; + EOQ +} + +variable "keyvault_with_rbac_keys_expiration_not_set_trigger_enabled" { + type = bool + default = false + description = "If true, the trigger is enabled." + + tags = { + folder = "Advanced/KeyVault" + } +} + +variable "keyvault_with_rbac_keys_expiration_not_set_trigger_schedule" { + type = string + default = "15m" + description = "If the trigger is enabled, run it on this schedule." + + tags = { + folder = "Advanced/KeyVault" + } +} + +trigger "query" "detect_and_correct_keyvault_with_rbac_keys_expiration_not_set" { + title = "Detect & correct Key Vaults with RBAC keys without expiration date" + description = "Detect Key Vaults with RBAC keys that do not have an expiration date set and then set expiration date." + tags = local.keyvault_common_tags + + enabled = var.keyvault_with_rbac_keys_expiration_not_set_trigger_enabled + schedule = var.keyvault_with_rbac_keys_expiration_not_set_trigger_schedule + database = var.database + sql = local.keyvault_with_rbac_keys_expiration_not_set_query + + capture "insert" { + pipeline = pipeline.correct_keyvault_with_rbac_keys_expiration_not_set + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_keyvault_with_rbac_keys_expiration_not_set" { + title = "Detect & correct Key Vaults with RBAC keys without expiration date" + description = "Detect Key Vaults with RBAC keys that do not have an expiration date set and then set expiration date." + tags = local.keyvault_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.keyvault_with_rbac_keys_expiration_not_set_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_keyvault_with_rbac_keys_expiration_not_set + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_keyvault_with_rbac_keys_expiration_not_set" { + title = "Correct Key Vaults with RBAC keys without expiration date" + description = "Runs corrective action on a collection of Key Vaults with RBAC keys without expiration date." + tags = merge(local.keyvault_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} Key Vaults with RBAC keys without expiration date." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected Key Vault ${each.value.title} with RBAC keys without expiration date." + } +} + diff --git a/pipelines/keyvault/keyvault_with_rbac_secrets_expiration_not_set.fp b/pipelines/keyvault/keyvault_with_rbac_secrets_expiration_not_set.fp new file mode 100644 index 0000000..66db30e --- /dev/null +++ b/pipelines/keyvault/keyvault_with_rbac_secrets_expiration_not_set.fp @@ -0,0 +1,144 @@ +locals { + keyvault_with_rbac_secrets_expiration_not_set_query = <<-EOQ + with rbac_vault as ( + select + name + from + azure_key_vault + where + enable_rbac_authorization + ) + select + concat(kvs.id, ' [', kvs.subscription_id, '/', kvs.resource_group, ']') as title, + kvs.id as id, + kvs.name, + kvs.subscription_id, + kvs.vault_name as vault_name, + kvs._ctx ->> 'connection_name' as conn + from + azure_key_vault_secret kvs + left join rbac_vault as v on v.name = kvs.vault_name + where + kvs.enabled + and kvs.expires_at is null + and v.name is not null; + EOQ + +} + +variable "keyvault_with_rbac_secrets_expiration_not_set_trigger_enabled" { + type = bool + default = false + description = "If true, the trigger is enabled." + + tags = { + folder = "Advanced/KeyVault" + } +} + +variable "keyvault_with_rbac_secrets_expiration_not_set_trigger_schedule" { + type = string + default = "15m" + description = "If the trigger is enabled, run it on this schedule." + + tags = { + folder = "Advanced/KeyVault" + } +} + +trigger "query" "detect_and_correct_keyvault_with_rbac_secrets_expiration_not_set" { + title = "Detect & correct Key Vaults with RBAC secrets without expiration date" + description = "Detect Key Vaults with RBAC secrets that do not have an expiration date set and then set expiration date." + tags = local.keyvault_common_tags + + enabled = var.keyvault_with_rbac_secrets_expiration_not_set_trigger_enabled + schedule = var.keyvault_with_rbac_secrets_expiration_not_set_trigger_schedule + database = var.database + sql = local.keyvault_with_rbac_secrets_expiration_not_set_query + + capture "insert" { + pipeline = pipeline.correct_keyvault_with_rbac_secrets_expiration_not_set + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_keyvault_with_rbac_secrets_expiration_not_set" { + title = "Detect & correct Key Vaults with RBAC secrets without expiration date" + description = "Detect Key Vaults with RBAC secrets that do not have an expiration date set and then set expiration date." + tags = local.keyvault_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.keyvault_with_rbac_secrets_expiration_not_set_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_keyvault_with_rbac_secrets_expiration_not_set + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_keyvault_with_rbac_secrets_expiration_not_set" { + title = "Correct Key Vaults with RBAC secrets without expiration date" + description = "Runs corrective action on a collection of Key Vaults with RBAC secrets without expiration date." + tags = merge(local.keyvault_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} Key Vaults with RBAC secrets without expiration date." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected Key Vault ${each.value.title} with RBAC secrets without expiration date." + } +} diff --git a/pipelines/monitor/monitor.fp b/pipelines/monitor/monitor.fp new file mode 100644 index 0000000..e212bf0 --- /dev/null +++ b/pipelines/monitor/monitor.fp @@ -0,0 +1,5 @@ +locals { + monitor_common_tags = merge(local.azure_compliance_common_tags, { + service = "Azure/Monitor" + }) +} diff --git a/pipelines/monitor/monitor_storage_containers_insights_activity_logs_not_encrypted_with_cmk.fp b/pipelines/monitor/monitor_storage_containers_insights_activity_logs_not_encrypted_with_cmk.fp new file mode 100644 index 0000000..d09ea5f --- /dev/null +++ b/pipelines/monitor/monitor_storage_containers_insights_activity_logs_not_encrypted_with_cmk.fp @@ -0,0 +1,137 @@ + +locals { + monitor_storage_containers_insights_activity_logs_not_encrypted_with_cmk_query = <<-EOQ + select + concat(a.id, ' [', a.subscription_id, '/', a.resource_group, ']') as title, + a.id as id, + a.name, + a.resource_group, + a.subscription_id, + a._ctx ->> 'connection_name' as conn + from + azure_storage_container c, + azure_storage_account a + where + c.name = 'insights-activity-logs' + and c.account_name = a.name + and a.encryption_key_source <> 'Microsoft.Keyvault'; + EOQ +} + +variable "monitor_storage_containers_insights_activity_logs_not_encrypted_with_cmk_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/Monitor" + } +} + +variable "monitor_storage_containers_insights_activity_logs_not_encrypted_with_cmk_trigger_schedule" { + type = string + default = "15m" + description = "If the trigger is enabled, run it on this schedule." + + tags = { + folder = "Advanced/Monitor" + } +} + +trigger "query" "detect_and_correct_monitor_storage_containers_insights_activity_logs_not_encrypted_with_cmk" { + title = "Detect & correct Storage account containers insights activity logs not encrypted with CMK" + description = "Detect Storage account containers insights activity logs not encrypted with CMK and then enable encryption using CMK." + + tags = local.monitor_common_tags + + enabled = var.monitor_storage_containers_insights_activity_logs_not_encrypted_with_cmk_trigger_enabled + schedule = var.monitor_storage_containers_insights_activity_logs_not_encrypted_with_cmk_trigger_schedule + database = var.database + sql = local.monitor_storage_containers_insights_activity_logs_not_encrypted_with_cmk_query + + capture "insert" { + pipeline = pipeline.correct_monitor_storage_containers_insights_activity_logs_not_encrypted_with_cmk + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_monitor_storage_containers_insights_activity_logs_not_encrypted_with_cmk" { + title = "Detect & correct Storage account containers insights activity logs not encrypted with CMK" + description = "Detect Storage containers insights activity logs not encrypted with CMK and then enable encryption using CMK." + + tags = local.monitor_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.monitor_storage_containers_insights_activity_logs_not_encrypted_with_cmk_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_monitor_storage_containers_insights_activity_logs_not_encrypted_with_cmk + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_monitor_storage_containers_insights_activity_logs_not_encrypted_with_cmk" { + title = "Correct Storage account containers insights activity logs not encrypted with CMK" + description = "Executes corrective actions on Storage account containers insights activity logs not encrypted with CMK." + tags = merge(local.monitor_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + conn = string + })) + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} Storage account container(s) insights activity logs not encrypted with CMK." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected Storage account ${each.value.title} container(s) insights activity logs not encrypted with CMK." + } +} \ No newline at end of file diff --git a/pipelines/monitor/subscriptions_diagnostic_setting_without_capturing_proper_categories.fp b/pipelines/monitor/subscriptions_diagnostic_setting_without_capturing_proper_categories.fp new file mode 100644 index 0000000..e97dcf3 --- /dev/null +++ b/pipelines/monitor/subscriptions_diagnostic_setting_without_capturing_proper_categories.fp @@ -0,0 +1,162 @@ +locals { + subscriptions_diagnostic_setting_without_capturing_proper_categories_query = <<-EOQ + with enabled_settings as ( + select + name, + id, + _ctx, + resource_group, + subscription_id, + count(*) filter (where l ->> 'enabled' = 'true' + and l ->> 'category' in ('Administrative', 'Security', 'Alert', 'Policy') + ) as valid_category_count, + string_agg(l ->> 'category', ', ') filter (where l ->> 'enabled' = 'true' + and l ->> 'category' in ('Administrative', 'Security', 'Alert', 'Policy') + ) as valid_categories + from + azure_diagnostic_setting, + jsonb_array_elements(logs) as l + group by + name, + id, + _ctx, + resource_group, + subscription_id + ), enabled_settings_count as ( + select + subscription_id, + count(*) as enabled_setting_counts + from + enabled_settings + where + valid_category_count = 4 + group by + subscription_id + ) + select + distinct sub.subscription_id as title, + sub._ctx ->> 'connection_name' as conn + from + azure_subscription sub + left join enabled_settings_count as i on i.subscription_id = sub.subscription_id + where + enabled_setting_counts = 0 or enabled_setting_counts is null; + EOQ +} + +variable "subscriptions_diagnostic_setting_without_capturing_proper_categories_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/Monitor" + } +} + +variable "subscriptions_diagnostic_setting_without_capturing_proper_categories_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/Monitor" + } +} + +trigger "query" "detect_and_correct_subscriptions_diagnostic_setting_without_capturing_proper_categories" { + title = "Detect & correct Subscriptions diagnostic settings without capturing proper categories" + description = "Detect subscriptions diagnostic settings without capturing proper categories" + tags = local.monitor_common_tags + + enabled = var.subscriptions_diagnostic_setting_without_capturing_proper_categories_trigger_enabled + schedule = var.subscriptions_diagnostic_setting_without_capturing_proper_categories_trigger_schedule + database = var.database + sql = local.subscriptions_diagnostic_setting_without_capturing_proper_categories_query + + capture "insert" { + pipeline = pipeline.correct_subscriptions_diagnostic_setting_without_capturing_proper_categories + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_subscriptions_diagnostic_setting_without_capturing_proper_categories" { + title = "Detect & correct Subscriptions diagnostic settings without capturing proper categories" + description = "Detect subscriptions diagnostic settings without capturing proper categories." + tags = local.monitor_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.subscriptions_diagnostic_setting_without_capturing_proper_categories_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_subscriptions_diagnostic_setting_without_capturing_proper_categories + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_subscriptions_diagnostic_setting_without_capturing_proper_categories" { + title = "Correct Subscriptions diagnostic settings without capturing proper categories" + description = "Send notifications for subscriptions diagnostic settings without capturing proper categories." + tags = merge(local.monitor_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} subscription(s) diagnostic settings without capturing proper categories." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected subscription ${each.value.title} diagnostic settings without capturing proper categories." + } +} \ No newline at end of file diff --git a/pipelines/monitor/subscriptions_without_activity_log_alert_for_create_policy_assignment.fp b/pipelines/monitor/subscriptions_without_activity_log_alert_for_create_policy_assignment.fp new file mode 100644 index 0000000..a0178e8 --- /dev/null +++ b/pipelines/monitor/subscriptions_without_activity_log_alert_for_create_policy_assignment.fp @@ -0,0 +1,160 @@ +locals { + subscriptions_without_activity_log_alert_for_create_policy_assignment_query = <<-EOQ + with alert_rule as ( + select + alert.id as alert_id, + alert.name as alert_name, + alert.enabled, + alert.location, + alert.subscription_id, + alert.resource_group, + alert._ctx ->> 'connection_name' as conn, + jsonb_array_length(alert.condition -> 'allOf') + from + azure_log_alert as alert, + jsonb_array_elements_text(scopes) as sc + where + alert.location = 'Global' + and alert.enabled + and sc = '/subscriptions/' || alert.subscription_id + and alert.condition -> 'allOf' @> '[{"equals":"Administrative","field":"category"}]' + and alert.condition -> 'allOf' @> '[{"field": "operationName", "equals": "Microsoft.Authorization/policyAssignments/write"}]' + limit + 1 + ) + select + sub.subscription_id as title, + sub._ctx ->> 'connection_name' as conn + from + azure_subscription sub + left join alert_rule a on sub.subscription_id = a.subscription_id + group by + sub.subscription_id, + sub.display_name, + sub._ctx, + a.alert_id, + a.alert_name, + a.resource_group, + a.subscription_id, + a.conn + having + not(count(a.subscription_id) > 0); + EOQ +} + +variable "subscriptions_without_activity_log_alert_for_create_policy_assignment_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/Monitor" + } +} + +variable "subscriptions_without_activity_log_alert_for_create_policy_assignment_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/Monitor" + } +} + +trigger "query" "detect_and_correct_subscriptions_without_activity_log_alert_for_create_policy_assignment" { + title = "Detect & correct Subscriptions without activity log alert for create policy assignment" + description = "Detect subscriptions without an activity log alert for create policy assignment." + tags = local.monitor_common_tags + + enabled = var.subscriptions_without_activity_log_alert_for_create_policy_assignment_trigger_enabled + schedule = var.subscriptions_without_activity_log_alert_for_create_policy_assignment_trigger_schedule + database = var.database + sql = local.subscriptions_without_activity_log_alert_for_create_policy_assignment_query + + capture "insert" { + pipeline = pipeline.correct_subscriptions_without_activity_log_alert_for_create_policy_assignment + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_subscriptions_without_activity_log_alert_for_create_policy_assignment" { + title = "Detect & correct Subscriptions without activity log alert for create policy assignment" + description = "Detect subscriptions without an activity log alert for create policy assignment." + tags = local.monitor_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.subscriptions_without_activity_log_alert_for_create_policy_assignment_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_subscriptions_without_activity_log_alert_for_create_policy_assignment + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_subscriptions_without_activity_log_alert_for_create_policy_assignment" { + title = "Correct Subscriptions without activity log alert for create policy assignment" + description = "Send notifications for subscriptions without a activity log alert for create policy assignment." + tags = merge(local.monitor_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} Subscription(s) without activity log alert for create policy assignment." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected Subscription ${each.value.title} without activity log alert for create policy assignment" + } +} \ No newline at end of file diff --git a/pipelines/monitor/subscriptions_without_activity_log_alert_for_create_update_nsg.fp b/pipelines/monitor/subscriptions_without_activity_log_alert_for_create_update_nsg.fp new file mode 100644 index 0000000..ef00cc9 --- /dev/null +++ b/pipelines/monitor/subscriptions_without_activity_log_alert_for_create_update_nsg.fp @@ -0,0 +1,171 @@ +locals { + subscriptions_without_activity_log_alert_for_create_update_nsg_query = <<-EOQ + with alert_rule as ( + select + alert.id as alert_id, + alert.name as alert_name, + alert.enabled, + alert.location, + alert.subscription_id, + alert.resource_group, + alert._ctx ->> 'connection_name' as conn, + jsonb_array_length(alert.condition -> 'allOf') + from + azure_log_alert as alert, + jsonb_array_elements_text(scopes) as sc + where + alert.location = 'Global' + and alert.enabled + and sc = '/subscriptions/' || alert.subscription_id + and ( + ( + alert.condition -> 'allOf' @> '[{"equals":"Administrative","field":"category"}]' + and alert.condition -> 'allOf' @> '[{"field": "operationName", "equals": "Microsoft.Network/networkSecurityGroups/write"}]' + ) + or ( + alert.condition -> 'allOf' @> '[{"equals":"Administrative","field":"category"}]' + and alert.condition -> 'allOf' @> '[{"field": "resourceType", "equals": "microsoft.network/networksecuritygroups"}]' + and jsonb_array_length(alert.condition -> 'allOf') = 2 + ) + ) + limit + 1 + ) + select + sub.subscription_id as title, + sub._ctx ->> 'connection_name' as conn + from + azure_subscription sub + left join alert_rule a on sub.subscription_id = a.subscription_id + group by + sub.subscription_id, + sub.display_name, + sub._ctx, + a.alert_id, + a.alert_name, + a.resource_group, + a.subscription_id, + a.conn + having + not(count(a.subscription_id) > 0); + EOQ + +} + +variable "subscriptions_without_activity_log_alert_for_create_update_nsg_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/Monitor" + } +} + +variable "subscriptions_without_activity_log_alert_for_create_update_nsg_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/Monitor" + } +} + + +trigger "query" "detect_and_correct_subscriptions_without_activity_log_alert_for_create_update_nsg" { + title = "Detect & correct Subscriptions without activity log alert for create and update NSG" + description = "Detect subscriptions without an activity log alert for create and update NSG." + tags = local.monitor_common_tags + + enabled = var.subscriptions_without_activity_log_alert_for_create_update_nsg_trigger_enabled + schedule = var.subscriptions_without_activity_log_alert_for_create_update_nsg_trigger_schedule + database = var.database + sql = local.subscriptions_without_activity_log_alert_for_create_update_nsg_query + + capture "insert" { + pipeline = pipeline.correct_subscriptions_without_activity_log_alert_for_create_update_nsg + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_subscriptions_without_activity_log_alert_for_create_update_nsg" { + title = "Detect & correct Subscriptions without activity log alert for create and update NSG" + description = "Detect subscriptions without an activity log alert for create and update NSG." + tags = local.monitor_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.subscriptions_without_activity_log_alert_for_create_update_nsg_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_subscriptions_without_activity_log_alert_for_create_update_nsg + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_subscriptions_without_activity_log_alert_for_create_update_nsg" { + title = "Correct Subscriptions without activity log alert for create and update NSG" + description = "Send notifications for subscriptions without activity log alert for create and update NSG." + tags = merge(local.monitor_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} subscription(s) without activity log alert for create and update NSG." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected subscription ${each.value.title} without activity log alert for create and update NSG." + } +} diff --git a/pipelines/monitor/subscriptions_without_activity_log_alert_for_create_update_security_solution.fp b/pipelines/monitor/subscriptions_without_activity_log_alert_for_create_update_security_solution.fp new file mode 100644 index 0000000..a7e3d60 --- /dev/null +++ b/pipelines/monitor/subscriptions_without_activity_log_alert_for_create_update_security_solution.fp @@ -0,0 +1,182 @@ +locals { + subscriptions_without_activity_log_alert_for_create_update_security_solution_query = <<-EOQ + with alert_rule as ( + select + alert.id as alert_id, + alert.name as alert_name, + alert.enabled, + alert.location, + alert.subscription_id, + alert.resource_group, + alert._ctx ->> 'connection_name' as conn, + jsonb_array_length(alert.condition -> 'allOf') + from + azure_log_alert as alert, + jsonb_array_elements_text(scopes) as sc + where + alert.location = 'Global' + and alert.enabled + and sc = '/subscriptions/' || alert.subscription_id + and ( + ( + alert.condition -> 'allOf' @> '[{"equals":"Security","field":"category"}]' + and alert.condition -> 'allOf' @> '[{"field": "operationName", "equals": "Microsoft.Security/securitySolutions/write"}]' + ) + or ( + alert.condition -> 'allOf' @> '[{"equals":"Security","field":"category"}]' + and alert.condition -> 'allOf' @> '[{"field": "resourceType", "equals": "microsoft.security/securitysolutions"}]' + and jsonb_array_length(alert.condition -> 'allOf') = 2 + ) + ) + limit + 1 + ), resource_group as ( + select + distinct on (s.subscription_id) + r.name, + r.subscription_id + from + azure_subscription as s + left join azure_resource_group as r on r.subscription_id = s.subscription_id + order by + s.subscription_id, r.name + ) + select + sub.subscription_id as title, + sub._ctx ->> 'connection_name' as conn + from + azure_subscription sub + left join alert_rule a on sub.subscription_id = a.subscription_id + left join resource_group as r on r.subscription_id = sub.subscription_id + group by + sub.subscription_id, + sub.display_name, + sub._ctx, + a.alert_id, + a.alert_name, + a.resource_group, + a.subscription_id, + a.conn, + r.name + having + not(count(a.subscription_id) > 0); + EOQ + +} + +variable "subscriptions_without_activity_log_alert_for_create_update_security_solution_trigger_enabled" { + type = bool + default = false + description = "If true, the trigger is enabled." + + tags = { + folder = "Advanced/Monitor" + } +} + +variable "subscriptions_without_activity_log_alert_for_create_update_security_solution_trigger_schedule" { + type = string + default = "15m" + description = "If the trigger is enabled, run it on this schedule." + + tags = { + folder = "Advanced/Monitor" + } +} + +trigger "query" "detect_and_correct_subscriptions_without_activity_log_alert_for_create_update_security_solution" { + title = "Detect & correct Subscriptions without activity log alert for create and update security solution" + description = "Detect subscriptions without an activity log alert for create and update security solution." + tags = local.monitor_common_tags + + enabled = var.subscriptions_without_activity_log_alert_for_create_update_security_solution_trigger_enabled + schedule = var.subscriptions_without_activity_log_alert_for_create_update_security_solution_trigger_schedule + database = var.database + sql = local.subscriptions_without_activity_log_alert_for_create_update_security_solution_query + + capture "insert" { + pipeline = pipeline.correct_subscriptions_without_activity_log_alert_for_create_update_security_solution + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_subscriptions_without_activity_log_alert_for_create_update_security_solution" { + title = "Detect & correct Subscriptions without activity log alert for create and update security solution" + description = "Detect subscriptions without an activity log alert for create and update security solution." + tags = local.monitor_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.subscriptions_without_activity_log_alert_for_create_update_security_solution_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_subscriptions_without_activity_log_alert_for_create_update_security_solution + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_subscriptions_without_activity_log_alert_for_create_update_security_solution" { + title = "Correct Subscriptions without activity log alert for create and update security solution" + description = "Send notifications for subscriptions without activity log alert for create and update security solution." + tags = merge(local.monitor_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} subscription(s) without activity log alert for create and update security solution." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected subscription ${each.value.title} without activity log alert for create and update security solution." + } +} diff --git a/pipelines/monitor/subscriptions_without_activity_log_alert_for_create_update_sql_servers_firewall_rule.fp b/pipelines/monitor/subscriptions_without_activity_log_alert_for_create_update_sql_servers_firewall_rule.fp new file mode 100644 index 0000000..46f238f --- /dev/null +++ b/pipelines/monitor/subscriptions_without_activity_log_alert_for_create_update_sql_servers_firewall_rule.fp @@ -0,0 +1,170 @@ +locals { + subscriptions_without_activity_log_alert_for_create_update_sql_servers_firewall_rule_query = <<-EOQ + with alert_rule as ( + select + alert.id as alert_id, + alert.name as alert_name, + alert.enabled, + alert.location, + alert.subscription_id, + alert.resource_group, + alert._ctx ->> 'connection_name' as conn, + jsonb_array_length(alert.condition -> 'allOf') + from + azure_log_alert as alert, + jsonb_array_elements_text(scopes) as sc + where + alert.location = 'Global' + and alert.enabled + and sc = '/subscriptions/' || alert.subscription_id + and ( + ( + alert.condition -> 'allOf' @> '[{"equals":"Administrative","field":"category"}]' + and alert.condition -> 'allOf' @> '[{"field": "operationName", "equals": "Microsoft.Sql/servers/firewallRules/write"}]' + ) + or ( + alert.condition -> 'allOf' @> '[{"equals":"Administrative","field":"category"}]' + and alert.condition -> 'allOf' @> '[{"field": "resourceType", "equals": "microsoft.sql/servers/firewallrules"}]' + and jsonb_array_length(alert.condition -> 'allOf') = 2 + ) + ) + limit + 1 + ) + select + sub.subscription_id as title, + sub._ctx ->> 'connection_name' as conn + from + azure_subscription sub + left join alert_rule a on sub.subscription_id = a.subscription_id + group by + sub.subscription_id, + sub.display_name, + sub._ctx, + a.alert_id, + a.alert_name, + a.resource_group, + a.subscription_id, + a.conn + having + not(count(a.subscription_id) > 0); + EOQ + +} + +variable "subscriptions_without_activity_log_alert_for_create_update_sql_servers_firewall_rule_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/Monitor" + } +} + +variable "subscriptions_without_activity_log_alert_for_create_update_sql_servers_firewall_rule_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/Monitor" + } +} + +trigger "query" "detect_and_correct_subscriptions_without_activity_log_alert_for_create_update_sql_servers_firewall_rule" { + title = "Detect & correct Subscriptions without activity log alert for create and update SQL servers firewall rule" + description = "Detect subscriptions without an activity log alert for create and update SQL servers firewall rule." + tags = local.monitor_common_tags + + enabled = var.subscriptions_without_activity_log_alert_for_create_update_sql_servers_firewall_rule_trigger_enabled + schedule = var.subscriptions_without_activity_log_alert_for_create_update_sql_servers_firewall_rule_trigger_schedule + database = var.database + sql = local.subscriptions_without_activity_log_alert_for_create_update_sql_servers_firewall_rule_query + + capture "insert" { + pipeline = pipeline.correct_subscriptions_without_activity_log_alert_for_create_update_sql_servers_firewall_rule + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_subscriptions_without_activity_log_alert_for_create_update_sql_servers_firewall_rule" { + title = "Detect & correct Subscriptions without activity log alert for create and update SQL servers firewall rule" + description = "Detect subscriptions without an activity log alert for create and update SQL servers firewall rule." + tags = local.monitor_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.subscriptions_without_activity_log_alert_for_create_update_sql_servers_firewall_rule_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_subscriptions_without_activity_log_alert_for_create_update_sql_servers_firewall_rule + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_subscriptions_without_activity_log_alert_for_create_update_sql_servers_firewall_rule" { + title = "Correct Subscriptions without activity log alert for create and update SQL servers firewall rule" + description = "Send notifications for subscriptions without activity log alert for create and update SQL servers firewall rule." + tags = merge(local.monitor_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} subscription(s) without activity log alert for create and update SQL servers firewall rule." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected subscription ${each.value.title} without activity log alert for create and update SQL servers firewall rule." + } +} \ No newline at end of file diff --git a/pipelines/monitor/subscriptions_without_activity_log_alert_for_delete_nsg.fp b/pipelines/monitor/subscriptions_without_activity_log_alert_for_delete_nsg.fp new file mode 100644 index 0000000..964e5aa --- /dev/null +++ b/pipelines/monitor/subscriptions_without_activity_log_alert_for_delete_nsg.fp @@ -0,0 +1,169 @@ +locals { + subscriptions_without_activity_log_alert_for_delete_nsg_query = <<-EOQ + with alert_rule as ( + select + alert.id as alert_id, + alert.name as alert_name, + alert.enabled, + alert.location, + alert.subscription_id, + alert.resource_group, + alert._ctx ->> 'connection_name' as conn, + jsonb_array_length(alert.condition -> 'allOf') + from + azure_log_alert as alert, + jsonb_array_elements_text(scopes) as sc + where + alert.location = 'Global' + and alert.enabled + and sc = '/subscriptions/' || alert.subscription_id + and ( + ( + alert.condition -> 'allOf' @> '[{"equals":"Administrative","field":"category"}]' + and alert.condition -> 'allOf' @> '[{"field": "operationName", "equals": "Microsoft.Network/networkSecurityGroups/delete"}]' + ) + or ( + alert.condition -> 'allOf' @> '[{"equals":"Administrative","field":"category"}]' + and alert.condition -> 'allOf' @> '[{"field": "resourceType", "equals": "microsoft.network/networksecuritygroups"}]' + and jsonb_array_length(alert.condition -> 'allOf') = 2 + ) + ) + limit + 1 + ) + select + sub.subscription_id as title, + sub._ctx ->> 'connection_name' as conn + from + azure_subscription sub + left join alert_rule a on sub.subscription_id = a.subscription_id + group by + sub.subscription_id, + sub.display_name, + sub._ctx, + a.alert_id, + a.alert_name, + a.resource_group, + a.subscription_id, + a.conn + having + not(count(a.subscription_id) > 0); + EOQ +} + +variable "subscriptions_without_activity_log_alert_for_delete_nsg_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/Monitor" + } +} + +variable "subscriptions_without_activity_log_alert_for_delete_nsg_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/Monitor" + } +} + +trigger "query" "detect_and_correct_subscriptions_without_activity_log_alert_for_delete_nsg" { + title = "Detect & correct Subscriptions without activity log alert for delete NSG" + description = "Detect subscriptions without an activity log alert for delete NSG." + tags = local.monitor_common_tags + + enabled = var.subscriptions_without_activity_log_alert_for_delete_nsg_trigger_enabled + schedule = var.subscriptions_without_activity_log_alert_for_delete_nsg_trigger_schedule + database = var.database + sql = local.subscriptions_without_activity_log_alert_for_delete_nsg_query + + capture "insert" { + pipeline = pipeline.correct_subscriptions_without_activity_log_alert_for_delete_nsg + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_subscriptions_without_activity_log_alert_for_delete_nsg" { + title = "Detect & correct Subscriptions without activity log alert for delete NSG" + description = "Detect subscriptions without an activity log alert for delete NSG." + tags = local.monitor_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.subscriptions_without_activity_log_alert_for_delete_nsg_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_subscriptions_without_activity_log_alert_for_delete_nsg + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_subscriptions_without_activity_log_alert_for_delete_nsg" { + title = "Correct Subscriptions without activity log alert for delete NSG" + description = "Send notifications for subscriptions without activity log alert for delete NSG." + tags = merge(local.monitor_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} subscription(s) without activity log alert for delete NSG." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected subscription ${each.value.title} wwithout activity log alert for delete NSG." + } +} diff --git a/pipelines/monitor/subscriptions_without_activity_log_alert_for_delete_policy_assignment.fp b/pipelines/monitor/subscriptions_without_activity_log_alert_for_delete_policy_assignment.fp new file mode 100644 index 0000000..4a08e9b --- /dev/null +++ b/pipelines/monitor/subscriptions_without_activity_log_alert_for_delete_policy_assignment.fp @@ -0,0 +1,160 @@ +locals { + subscriptions_without_activity_log_alert_for_delete_policy_assignment_query = <<-EOQ + with alert_rule as ( + select + alert.id as alert_id, + alert.name as alert_name, + alert.enabled, + alert.location, + alert.subscription_id, + alert.resource_group, + alert._ctx ->> 'connection_name' as conn, + jsonb_array_length(alert.condition -> 'allOf') + from + azure_log_alert as alert, + jsonb_array_elements_text(scopes) as sc + where + alert.location = 'Global' + and alert.enabled + and sc = '/subscriptions/' || alert.subscription_id + and alert.condition -> 'allOf' @> '[{"equals":"Administrative","field":"category"}]' + and alert.condition -> 'allOf' @> '[{"field": "operationName", "equals": "Microsoft.Authorization/policyAssignments/delete"}]' + limit + 1 + ) + select + sub.subscription_id as title, + sub._ctx ->> 'connection_name' as conn + from + azure_subscription sub + left join alert_rule a on sub.subscription_id = a.subscription_id + group by + sub.subscription_id, + sub.display_name, + sub._ctx, + a.alert_id, + a.alert_name, + a.resource_group, + a.subscription_id, + a.conn + having + not(count(a.subscription_id) > 0); + EOQ +} + +variable "subscriptions_without_activity_log_alert_for_delete_policy_assignment_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/Monitor" + } +} + +variable "subscriptions_without_activity_log_alert_for_delete_policy_assignment_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/Monitor" + } +} + +trigger "query" "detect_and_correct_subscriptions_without_activity_log_alert_for_delete_policy_assignment" { + title = "Detect & correct Subscriptions without activity log alert for delete policy assignment" + description = "Detect subscriptions without an activity log alert for delete policy assignment." + tags = local.monitor_common_tags + + enabled = var.subscriptions_without_activity_log_alert_for_delete_policy_assignment_trigger_enabled + schedule = var.subscriptions_without_activity_log_alert_for_delete_policy_assignment_trigger_schedule + database = var.database + sql = local.subscriptions_without_activity_log_alert_for_delete_policy_assignment_query + + capture "insert" { + pipeline = pipeline.correct_subscriptions_without_activity_log_alert_for_delete_policy_assignment + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_subscriptions_without_activity_log_alert_for_delete_policy_assignment" { + title = "Detect & correct Subscriptions without activity log alert for delete policy assignment" + description = "Detect subscriptions without an activity log alert for delete policy assignment." + tags = local.monitor_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.subscriptions_without_activity_log_alert_for_delete_policy_assignment_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_subscriptions_without_activity_log_alert_for_delete_policy_assignment + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_subscriptions_without_activity_log_alert_for_delete_policy_assignment" { + title = "Correct Subscriptions without activity log alert for delete policy assignment" + description = "Send notifications for subscriptions without activity log alert for delete policy assignment." + tags = merge(local.monitor_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} subscription(s) without activity log alert for delete policy assignment." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected subscription ${each.value.title} without activity log alert for delete policy assignment." + } +} diff --git a/pipelines/monitor/subscriptions_without_activity_log_alert_for_delete_public_ip_address.fp b/pipelines/monitor/subscriptions_without_activity_log_alert_for_delete_public_ip_address.fp new file mode 100644 index 0000000..dac16eb --- /dev/null +++ b/pipelines/monitor/subscriptions_without_activity_log_alert_for_delete_public_ip_address.fp @@ -0,0 +1,169 @@ +locals { + subscriptions_without_activity_log_alert_for_delete_public_ip_address_query = <<-EOQ + with alert_rule as ( + select + alert.id as alert_id, + alert.name as alert_name, + alert.enabled, + alert.location, + alert.subscription_id, + alert.resource_group, + alert._ctx ->> 'connection_name' as conn, + jsonb_array_length(alert.condition -> 'allOf') + from + azure_log_alert as alert, + jsonb_array_elements_text(scopes) as sc + where + alert.location = 'Global' + and alert.enabled + and sc = '/subscriptions/' || alert.subscription_id + and ( + ( + alert.condition -> 'allOf' @> '[{"equals":"Administrative","field":"category"}]' + and alert.condition -> 'allOf' @> '[{"field": "operationName", "equals": "Microsoft.Network/publicIPAddresses/delete"}]' + ) + or ( + alert.condition -> 'allOf' @> '[{"equals":"Administrative","field":"category"}]' + and alert.condition -> 'allOf' @> '[{"field": "resourceType", "equals": "microsoft.network/publicipaddresses"}]' + and jsonb_array_length(alert.condition -> 'allOf') = 2 + ) + ) + limit + 1 + ) + select + sub.subscription_id as title, + sub._ctx ->> 'connection_name' as conn + from + azure_subscription sub + left join alert_rule a on sub.subscription_id = a.subscription_id + group by + sub.subscription_id, + sub.display_name, + sub._ctx, + a.alert_id, + a.alert_name, + a.resource_group, + a.subscription_id, + a.conn + having + not(count(a.subscription_id) > 0); + EOQ +} + +variable "subscriptions_without_activity_log_alert_for_delete_public_ip_address_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/Monitor" + } +} + +variable "subscriptions_without_activity_log_alert_for_delete_public_ip_address_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/Monitor" + } +} + +trigger "query" "detect_and_correct_subscriptions_without_activity_log_alert_for_delete_public_ip_address" { + title = "Detect & correct Subscriptions without activity log alert for delete public IP address" + description = "Detect subscriptions without an activity log alert for delete public IP address." + tags = local.monitor_common_tags + + enabled = var.subscriptions_without_activity_log_alert_for_delete_public_ip_address_trigger_enabled + schedule = var.subscriptions_without_activity_log_alert_for_delete_public_ip_address_trigger_schedule + database = var.database + sql = local.subscriptions_without_activity_log_alert_for_delete_public_ip_address_query + + capture "insert" { + pipeline = pipeline.correct_subscriptions_without_activity_log_alert_for_delete_public_ip_address + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_subscriptions_without_activity_log_alert_for_delete_public_ip_address" { + title = "Detect & correct Subscriptions without activity log alert for delete public IP address" + description = "Detect subscriptions without an activity log alert for delete public IP address." + tags = local.monitor_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.subscriptions_without_activity_log_alert_for_delete_public_ip_address_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_subscriptions_without_activity_log_alert_for_delete_public_ip_address + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_subscriptions_without_activity_log_alert_for_delete_public_ip_address" { + title = "Correct Subscriptions without activity log alert for delete public IP address" + description = "Send notifications for subscriptions without activity log alert for delete public IP address." + tags = merge(local.monitor_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} subscription(s) without activity log alert for delete public IP address." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected subscription ${each.value.title} without activity log alert for delete public IP address." + } +} diff --git a/pipelines/monitor/subscriptions_without_activity_log_alert_for_delete_security_solution.fp b/pipelines/monitor/subscriptions_without_activity_log_alert_for_delete_security_solution.fp new file mode 100644 index 0000000..8a8f60e --- /dev/null +++ b/pipelines/monitor/subscriptions_without_activity_log_alert_for_delete_security_solution.fp @@ -0,0 +1,169 @@ +locals { + subscriptions_without_activity_log_alert_for_delete_security_solution_query = <<-EOQ + with alert_rule as ( + select + alert.id as alert_id, + alert.name as alert_name, + alert.enabled, + alert.location, + alert.subscription_id, + alert.resource_group, + alert._ctx ->> 'connection_name' as conn, + jsonb_array_length(alert.condition -> 'allOf') + from + azure_log_alert as alert, + jsonb_array_elements_text(scopes) as sc + where + alert.location = 'Global' + and alert.enabled + and sc = '/subscriptions/' || alert.subscription_id + and ( + ( + alert.condition -> 'allOf' @> '[{"equals":"Security","field":"category"}]' + and alert.condition -> 'allOf' @> '[{"field": "operationName", "equals": "Microsoft.Security/securitySolutions/delete"}]' + ) + or ( + alert.condition -> 'allOf' @> '[{"equals":"Security","field":"category"}]' + and alert.condition -> 'allOf' @> '[{"field": "resourceType", "equals": "microsoft.security/securitysolutions"}]' + and jsonb_array_length(alert.condition -> 'allOf') = 2 + ) + ) + limit + 1 + ) + select + sub.subscription_id as title, + sub._ctx ->> 'connection_name' as conn + from + azure_subscription sub + left join alert_rule a on sub.subscription_id = a.subscription_id + group by + sub.subscription_id, + sub.display_name, + sub._ctx, + a.alert_id, + a.alert_name, + a.resource_group, + a.subscription_id, + a.conn + having + not(count(a.subscription_id) > 0); + EOQ +} + +variable "subscriptions_without_activity_log_alert_for_delete_security_solution_trigger_enabled" { + type = bool + default = false + description = "If true, the trigger is enabled." + + tags = { + folder = "Advanced/Monitor" + } +} + +variable "subscriptions_without_activity_log_alert_for_delete_security_solution_trigger_schedule" { + type = string + default = "15m" + description = "If the trigger is enabled, run it on this schedule." + + tags = { + folder = "Advanced/Monitor" + } +} + +trigger "query" "detect_and_correct_subscriptions_without_activity_log_alert_for_delete_security_solution" { + title = "Detect & correct Subscriptions without activity log alert for delete security solution" + description = "Detect subscriptions without an activity log alert for delete security solution." + tags = local.monitor_common_tags + + enabled = var.subscriptions_without_activity_log_alert_for_delete_security_solution_trigger_enabled + schedule = var.subscriptions_without_activity_log_alert_for_delete_security_solution_trigger_schedule + database = var.database + sql = local.subscriptions_without_activity_log_alert_for_delete_security_solution_query + + capture "insert" { + pipeline = pipeline.correct_subscriptions_without_activity_log_alert_for_delete_security_solution + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_subscriptions_without_activity_log_alert_for_delete_security_solution" { + title = "Detect & correct Subscriptions without activity log alert for delete security solution" + description = "Detect subscriptions without an activity log alert for delete security solution." + tags = local.monitor_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.subscriptions_without_activity_log_alert_for_delete_security_solution_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_subscriptions_without_activity_log_alert_for_delete_security_solution + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_subscriptions_without_activity_log_alert_for_delete_security_solution" { + title = "Correct Subscriptions without activity log alert for delete security solution" + description = "Send notifications for subscriptions without activity log alert for delete security solution." + tags = merge(local.monitor_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} subscription(s) without activity log alert for delete security solution." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected subscription ${each.value.title} without activity log alert for delete security solution." + } +} diff --git a/pipelines/monitor/subscriptions_without_activity_log_alert_for_delete_sql_servers_firewall_rule.fp b/pipelines/monitor/subscriptions_without_activity_log_alert_for_delete_sql_servers_firewall_rule.fp new file mode 100644 index 0000000..eb468a1 --- /dev/null +++ b/pipelines/monitor/subscriptions_without_activity_log_alert_for_delete_sql_servers_firewall_rule.fp @@ -0,0 +1,169 @@ +locals { + subscriptions_without_activity_log_alert_for_delete_sql_servers_firewall_rule_query = <<-EOQ + with alert_rule as ( + select + alert.id as alert_id, + alert.name as alert_name, + alert.enabled, + alert.location, + alert.subscription_id, + alert.resource_group, + alert._ctx ->> 'connection_name' as conn, + jsonb_array_length(alert.condition -> 'allOf') + from + azure_log_alert as alert, + jsonb_array_elements_text(scopes) as sc + where + alert.location = 'Global' + and alert.enabled + and sc = '/subscriptions/' || alert.subscription_id + and ( + ( + alert.condition -> 'allOf' @> '[{"equals":"Administrative","field":"category"}]' + and alert.condition -> 'allOf' @> '[{"field": "operationName", "equals": "Microsoft.Sql/servers/firewallRules/delete"}]' + ) + or ( + alert.condition -> 'allOf' @> '[{"equals":"Administrative","field":"category"}]' + and alert.condition -> 'allOf' @> '[{"field": "resourceType", "equals": "microsoft.sql/servers/firewallrules"}]' + and jsonb_array_length(alert.condition -> 'allOf') = 2 + ) + ) + limit + 1 + ) + select + sub.subscription_id as title, + sub._ctx ->> 'connection_name' as conn + from + azure_subscription sub + left join alert_rule a on sub.subscription_id = a.subscription_id + group by + sub.subscription_id, + sub.display_name, + sub._ctx, + a.alert_id, + a.alert_name, + a.resource_group, + a.subscription_id, + a.conn + having + not(count(a.subscription_id) > 0); + EOQ +} + +variable "subscriptions_without_activity_log_alert_for_delete_sql_servers_firewall_rule_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/Monitor" + } +} + +variable "subscriptions_without_activity_log_alert_for_delete_sql_servers_firewall_rule_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/Monitor" + } +} + +trigger "query" "detect_and_correct_subscriptions_without_activity_log_alert_for_delete_sql_servers_firewall_rule" { + title = "Detect & correct Subscriptions without activity log alert for delete SQL servers firewall rule" + description = "Detect subscriptions without an activity log alert for delete SQL servers firewall rule." + tags = local.monitor_common_tags + + enabled = var.subscriptions_without_activity_log_alert_for_delete_sql_servers_firewall_rule_trigger_enabled + schedule = var.subscriptions_without_activity_log_alert_for_delete_sql_servers_firewall_rule_trigger_schedule + database = var.database + sql = local.subscriptions_without_activity_log_alert_for_delete_sql_servers_firewall_rule_query + + capture "insert" { + pipeline = pipeline.correct_subscriptions_without_activity_log_alert_for_delete_sql_servers_firewall_rule + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_subscriptions_without_activity_log_alert_for_delete_sql_servers_firewall_rule" { + title = "Detect & correct Subscriptions without activity log alert for delete SQL servers firewall rule" + description = "Detect subscriptions without an activity log alert for delete SQL servers firewall rule." + tags = local.monitor_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.subscriptions_without_activity_log_alert_for_delete_sql_servers_firewall_rule_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_subscriptions_without_activity_log_alert_for_delete_sql_servers_firewall_rule + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_subscriptions_without_activity_log_alert_for_delete_sql_servers_firewall_rule" { + title = "Correct Subscriptions without activity log alert for delete SQL servers firewall rule" + description = "Send notifications for subscriptions without activity log alert for delete SQL servers firewall rule." + tags = merge(local.monitor_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} subscription(s) without activity log alert for delete SQL servers firewall rule." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected subscription ${each.value.title} without activity log alert for delete SQL servers firewall rule." + } +} diff --git a/pipelines/monitor/subscriptions_without_activity_log_alert_for_update_public_ip_address.fp b/pipelines/monitor/subscriptions_without_activity_log_alert_for_update_public_ip_address.fp new file mode 100644 index 0000000..5bc5916 --- /dev/null +++ b/pipelines/monitor/subscriptions_without_activity_log_alert_for_update_public_ip_address.fp @@ -0,0 +1,170 @@ +locals { + subscriptions_without_activity_log_alert_for_update_public_ip_address_query = <<-EOQ + with alert_rule as ( + select + alert.id as alert_id, + alert.name as alert_name, + alert.enabled, + alert.location, + alert.subscription_id, + alert.resource_group, + alert._ctx ->> 'connection_name' as conn, + jsonb_array_length(alert.condition -> 'allOf') + from + azure_log_alert as alert, + jsonb_array_elements_text(scopes) as sc + where + alert.location = 'Global' + and alert.enabled + and sc = '/subscriptions/' || alert.subscription_id + and ( + ( + alert.condition -> 'allOf' @> '[{"equals":"Administrative","field":"category"}]' + and alert.condition -> 'allOf' @> '[{"field": "operationName", "equals": "Microsoft.Network/publicIPAddresses/write"}]' + ) + or ( + alert.condition -> 'allOf' @> '[{"equals":"Administrative","field":"category"}]' + and alert.condition -> 'allOf' @> '[{"field": "resourceType", "equals": "microsoft.network/publicipaddresses"}]' + and jsonb_array_length(alert.condition -> 'allOf') = 2 + ) + ) + limit + 1 + ) + select + sub.subscription_id as title, + sub._ctx ->> 'connection_name' as conn + from + azure_subscription sub + left join alert_rule a on sub.subscription_id = a.subscription_id + group by + sub.subscription_id, + sub.display_name, + sub._ctx, + a.alert_id, + a.alert_name, + a.resource_group, + a.subscription_id, + a.conn + having + not(count(a.subscription_id) > 0); + EOQ +} + +variable "subscriptions_without_activity_log_alert_for_update_public_ip_address_trigger_enabled" { + type = bool + default = false + description = "If true, the trigger is enabled." + + tags = { + folder = "Advanced/Monitor" + } +} + +variable "subscriptions_without_activity_log_alert_for_update_public_ip_address_trigger_schedule" { + type = string + default = "15m" + description = "If the trigger is enabled, run it on this schedule." + + tags = { + folder = "Advanced/Monitor" + } +} + +trigger "query" "detect_and_correct_subscriptions_without_activity_log_alert_for_update_public_ip_address" { + title = "Detect & correct Subscriptions without activity log alert for create update public IP address" + description = "Detect subscriptions without an activity log alert for create update public IP address." + tags = local.monitor_common_tags + + enabled = var.subscriptions_without_activity_log_alert_for_update_public_ip_address_trigger_enabled + schedule = var.subscriptions_without_activity_log_alert_for_update_public_ip_address_trigger_schedule + database = var.database + sql = local.subscriptions_without_activity_log_alert_for_update_public_ip_address_query + + capture "insert" { + pipeline = pipeline.correct_subscriptions_without_activity_log_alert_for_update_public_ip_address + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_subscriptions_without_activity_log_alert_for_update_public_ip_address" { + title = "Detect & correct Subscriptions without activity log alert for create update public IP address" + description = "Detect subscriptions without an activity log alert for create update public IP address." + tags = local.monitor_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.subscriptions_without_activity_log_alert_for_update_public_ip_address_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_subscriptions_without_activity_log_alert_for_update_public_ip_address + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_subscriptions_without_activity_log_alert_for_update_public_ip_address" { + title = "Correct Subscriptions without activity log alert for create update public IP address" + description = "Send notifications for subscriptions without activity log alert for create update public IP address." + tags = merge(local.monitor_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} subscriptions(s) without activity log alert for create update public IP address." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected subscription ${each.value.title} without activity log alert for create update public IP address." + } +} + diff --git a/pipelines/monitor/subscriptions_without_application_insight_configured.fp b/pipelines/monitor/subscriptions_without_application_insight_configured.fp new file mode 100644 index 0000000..302d8ce --- /dev/null +++ b/pipelines/monitor/subscriptions_without_application_insight_configured.fp @@ -0,0 +1,138 @@ +locals { + subscriptions_without_application_insight_configured_query = <<-EOQ + with application_insights as ( + select + subscription_id, + count(*) as no_application_insight + from + azure_application_insight + group by + subscription_id + ) + select + sub.subscription_id as title, + sub._ctx ->> 'connection_name' as conn + from + azure_subscription sub + left join application_insights as i on i.subscription_id = sub.subscription_id + where + i.subscription_id is null; + EOQ +} + +variable "subscriptions_without_application_insight_configured_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/Monitor" + } +} + +variable "subscriptions_without_application_insight_configured_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/Monitor" + } +} + +trigger "query" "detect_and_correct_subscriptions_without_application_insight_configured" { + title = "Detect & correct Subscriptions without application insight configured" + description = "Detect subscriptions without application insight configured." + tags = local.monitor_common_tags + + enabled = var.subscriptions_without_application_insight_configured_trigger_enabled + schedule = var.subscriptions_without_application_insight_configured_trigger_schedule + database = var.database + sql = local.subscriptions_without_application_insight_configured_query + + capture "insert" { + pipeline = pipeline.correct_subscriptions_without_application_insight_configured + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_subscriptions_without_application_insight_configured" { + title = "Detect & correct Subscriptions without application insight configured" + description = "Detect subscriptions without application insight configured." + tags = local.monitor_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.subscriptions_without_application_insight_configured_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_subscriptions_without_application_insight_configured + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_subscriptions_without_application_insight_configured" { + title = "Correct Subscriptions without application insight configured" + description = "Send notifications for subscriptions without application insight configured." + tags = merge(local.monitor_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} subscription(s) without application insight configured." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected subscription ${each.value.title} without application insight configured." + } +} \ No newline at end of file diff --git a/pipelines/mysql/mysql.fp b/pipelines/mysql/mysql.fp new file mode 100644 index 0000000..68a6e0d --- /dev/null +++ b/pipelines/mysql/mysql.fp @@ -0,0 +1,5 @@ +locals { + mysql_common_tags = merge(local.azure_compliance_common_tags, { + service = "Azure/MySQL" + }) +} diff --git a/pipelines/mysql/mysql_flexible_servers_with_audit_log_disabled.fp b/pipelines/mysql/mysql_flexible_servers_with_audit_log_disabled.fp new file mode 100644 index 0000000..4a32b3f --- /dev/null +++ b/pipelines/mysql/mysql_flexible_servers_with_audit_log_disabled.fp @@ -0,0 +1,324 @@ +locals { + mysql_flexible_servers_with_audit_log_disabled_query = <<-EOQ + select + concat(id, ' [', subscription_id, '/', resource_group, ']') as title, + id as id, + name as server_name, + resource_group, + subscription_id, + _ctx ->> 'connection_name' as conn + from + azure_mysql_flexible_server, + jsonb_array_elements(flexible_server_configurations) as config + where + config ->> 'Name' = 'audit_log_enabled' + and config -> 'ConfigurationProperties' ->> 'value' <> 'ON'; + EOQ + + mysql_flexible_servers_with_audit_log_disabled_enabled_actions_enum = ["skip", "enable_audit_log"] + mysql_flexible_servers_with_audit_log_disabled_default_action_enum = ["notify", "skip", "enable_audit_log"] +} + +variable "mysql_flexible_servers_with_audit_log_disabled_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/MySQL" + } +} + +variable "mysql_flexible_servers_with_audit_log_disabled_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/MySQL" + } +} + +variable "mysql_flexible_servers_with_audit_log_disabled_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + enum = ["notify", "skip", "enable_audit_log"] + + tags = { + folder = "Advanced/MySQL" + } +} + +variable "mysql_flexible_servers_with_audit_log_disabled_enabled_actions" { + type = list(string) + description = "The list of enabled actions to provide to approvers for selection." + default = ["skip", "enable_audit_log"] + enum = ["skip", "enable_audit_log"] + + tags = { + folder = "Advanced/MySQL" + } +} + +trigger "query" "detect_and_correct_mysql_flexible_servers_with_audit_log_disabled" { + title = "Detect & correct MySQL flexible servers with audit log disabled" + description = "Detect MySQL flexible servers with audit log disabled and then enable audit log." + tags = local.mysql_common_tags + + enabled = var.mysql_flexible_servers_with_audit_log_disabled_trigger_enabled + schedule = var.mysql_flexible_servers_with_audit_log_disabled_trigger_schedule + database = var.database + sql = local.mysql_flexible_servers_with_audit_log_disabled_query + + capture "insert" { + pipeline = pipeline.correct_mysql_flexible_servers_with_audit_log_disabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_mysql_flexible_servers_with_audit_log_disabled" { + title = "Detect & correct MySQL flexible servers with audit log disabled" + description = "Detect MySQL flexible servers with audit log disabled and then enable audit log." + tags = merge(local.mysql_common_tags, { recommended = "true" }) + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.mysql_flexible_servers_with_audit_log_disabled_default_action + enum = local.mysql_flexible_servers_with_audit_log_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.mysql_flexible_servers_with_audit_log_disabled_enabled_actions + enum = local.mysql_flexible_servers_with_audit_log_disabled_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.mysql_flexible_servers_with_audit_log_disabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_mysql_flexible_servers_with_audit_log_disabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_mysql_flexible_servers_with_audit_log_disabled" { + title = "Correct MySQL flexible servers with audit log disabled" + description = "Enable audit log for MySQL flexible servers with audit log disabled." + tags = merge(local.mysql_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + server_name = string + resource_group = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.mysql_flexible_servers_with_audit_log_disabled_default_action + enum = local.mysql_flexible_servers_with_audit_log_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.mysql_flexible_servers_with_audit_log_disabled_enabled_actions + enum = local.mysql_flexible_servers_with_audit_log_disabled_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} MySQL flexible server(s) with audit log disabled." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.id => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_mysql_flexible_server_with_audit_log_disabled + args = { + title = each.value.title + server_name = each.value.server_name + resource_group = each.value.resource_group + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_mysql_flexible_server_with_audit_log_disabled" { + title = "Correct MySQL flexible server with audit log disabled" + description = "Enable audit log for a MySQL flexible server with audit log disabled" + tags = merge(local.mysql_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "server_name" { + type = string + description = "The name of the MySQL flexible server." + } + + param "resource_group" { + type = string + description = local.description_resource_group + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.mysql_flexible_servers_with_audit_log_disabled_default_action + enum = local.mysql_flexible_servers_with_audit_log_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.mysql_flexible_servers_with_audit_log_disabled_enabled_actions + enum = local.mysql_flexible_servers_with_audit_log_disabled_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected MySQL flexible server ${param.title} with audit log disabled." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped MySQL flexible server ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "enable_audit_log" = { + label = "Enable audit log" + value = "enable_audit_log" + style = local.style_alert + pipeline_ref = azure.pipeline.set_mysql_flexible_server_parameter + pipeline_args = { + server_name = param.server_name + resource_group = param.resource_group + subscription_id = param.subscription_id + conn = param.conn + parameter_name = "audit_log_enabled" + parameter_value = "on" + } + success_msg = "Enabled audit log for MySQL flexible server ${param.title}." + error_msg = "Error enabling audit log for MySQL flexible server ${param.title}." + } + } + } + } +} diff --git a/pipelines/mysql/mysql_flexible_servers_with_audit_log_events_connection_not_set.fp b/pipelines/mysql/mysql_flexible_servers_with_audit_log_events_connection_not_set.fp new file mode 100644 index 0000000..1e64d08 --- /dev/null +++ b/pipelines/mysql/mysql_flexible_servers_with_audit_log_events_connection_not_set.fp @@ -0,0 +1,324 @@ +locals { + mysql_flexible_servers_with_audit_log_events_connection_not_set_query = <<-EOQ + select + concat(id, ' [', subscription_id, '/', resource_group, ']') as title, + id as id, + name as server_name, + resource_group, + subscription_id, + _ctx ->> 'connection_name' as conn + from + azure_mysql_flexible_server, + jsonb_array_elements(flexible_server_configurations) as config + where + config ->> 'Name' = 'audit_log_events' + and config -> 'ConfigurationProperties' ->> 'value' not in ('CONNECTION'); + EOQ + + mysql_flexible_servers_with_audit_log_events_connection_not_set_enabled_actions_enum = ["skip", "set_audit_log_events_connection"] + mysql_flexible_servers_with_audit_log_events_connection_not_set_default_action_enum = ["notify", "skip", "set_audit_log_events_connection"] +} + +variable "mysql_flexible_servers_with_audit_log_events_connection_not_set_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/MySQL" + } +} + +variable "mysql_flexible_servers_with_audit_log_events_connection_not_set_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/MySQL" + } +} + +variable "mysql_flexible_servers_with_audit_log_events_connection_not_set_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + enum = ["notify", "skip", "set_audit_log_events_connection"] + + tags = { + folder = "Advanced/MySQL" + } +} + +variable "mysql_flexible_servers_with_audit_log_events_connection_not_set_enabled_actions" { + type = list(string) + description = "The list of enabled actions to provide to approvers for selection." + default = ["skip", "set_audit_log_events_connection"] + enum = ["skip", "set_audit_log_events_connection"] + + tags = { + folder = "Advanced/MySQL" + } +} + +trigger "query" "detect_and_correct_mysql_flexible_servers_with_audit_log_events_connection_not_set" { + title = "Detect & correct MySQL flexible servers with audit log events not set to connection" + description = "Detect MySQL flexible servers with audit log not set to connection and then set audit log events to connection." + tags = merge(local.mysql_common_tags, { recommended = "true" }) + + enabled = var.mysql_flexible_servers_with_audit_log_events_connection_not_set_trigger_enabled + schedule = var.mysql_flexible_servers_with_audit_log_events_connection_not_set_trigger_schedule + database = var.database + sql = local.mysql_flexible_servers_with_audit_log_events_connection_not_set_query + + capture "insert" { + pipeline = pipeline.correct_mysql_flexible_servers_with_audit_log_events_connection_not_set + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_mysql_flexible_servers_with_audit_log_events_connection_not_set" { + title = "Detect & correct MySQL flexible servers with audit log events not set to connection" + description = "Detect MySQL flexible servers with audit log not set to connection and then set audit log events to connection." + tags = local.mysql_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.mysql_flexible_servers_with_audit_log_events_connection_not_set_default_action + enum = local.mysql_flexible_servers_with_audit_log_events_connection_not_set_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.mysql_flexible_servers_with_audit_log_events_connection_not_set_enabled_actions + enum = local.mysql_flexible_servers_with_audit_log_events_connection_not_set_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.mysql_flexible_servers_with_audit_log_events_connection_not_set_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_mysql_flexible_servers_with_audit_log_events_connection_not_set + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_mysql_flexible_servers_with_audit_log_events_connection_not_set" { + title = "Correct MySQL flexible servers with audit log events not set to connection" + description = "Set connection for MySQL flexible servers audit log events with audit log events not set to connection." + tags = merge(local.mysql_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + server_name = string + resource_group = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.mysql_flexible_servers_with_audit_log_events_connection_not_set_default_action + enum = local.mysql_flexible_servers_with_audit_log_events_connection_not_set_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.mysql_flexible_servers_with_audit_log_events_connection_not_set_enabled_actions + enum = local.mysql_flexible_servers_with_audit_log_events_connection_not_set_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} MySQL flexible server(s) with audit log events not set to connection." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.id => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_mysql_flexible_server_with_audit_log_events_connection_not_set + args = { + title = each.value.title + server_name = each.value.server_name + resource_group = each.value.resource_group + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_mysql_flexible_server_with_audit_log_events_connection_not_set" { + title = "Correct MySQL flexible server with audit log events not set to connection" + description = "Set connection for a MySQL flexible server audit log events with audit log events not set to connection." + tags = merge(local.mysql_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "server_name" { + type = string + description = "The name of the MySQL flexible server." + } + + param "resource_group" { + type = string + description = local.description_resource_group + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.mysql_flexible_servers_with_audit_log_events_connection_not_set_default_action + enum = local.mysql_flexible_servers_with_audit_log_events_connection_not_set_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.mysql_flexible_servers_with_audit_log_events_connection_not_set_enabled_actions + enum = local.mysql_flexible_servers_with_audit_log_events_connection_not_set_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected MySQL flexible server ${param.title} with audit log events not set to connection." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped MySQL flexible server ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "set_audit_log_events_connection" = { + label = "Set audit log events to connection " + value = "set_audit_log_events_connection" + style = local.style_alert + pipeline_ref = azure.pipeline.set_mysql_flexible_server_parameter + pipeline_args = { + server_name = param.server_name + resource_group = param.resource_group + subscription_id = param.subscription_id + conn = param.conn + parameter_name = "audit_log_events" + parameter_value = "CONNECTION" + } + success_msg = "Enabled connection to audit log events for MySQL flexible server ${param.title}." + error_msg = "Error enabling connection to audit log events for MySQL flexible server ${param.title}." + } + } + } + } +} diff --git a/pipelines/mysql/mysql_flexible_servers_with_ssl_disabled.fp b/pipelines/mysql/mysql_flexible_servers_with_ssl_disabled.fp new file mode 100644 index 0000000..f65e7e7 --- /dev/null +++ b/pipelines/mysql/mysql_flexible_servers_with_ssl_disabled.fp @@ -0,0 +1,324 @@ +locals { + mysql_flexible_servers_with_ssl_disabled_query = <<-EOQ + select + concat(id, ' [', subscription_id, '/', resource_group, ']') as title, + id as id, + name as server_name, + resource_group, + subscription_id, + _ctx ->> 'connection_name' as conn + from + azure_mysql_flexible_server, + jsonb_array_elements(flexible_server_configurations) as config + where + config ->> 'Name' = 'require_secure_transport' + and config -> 'ConfigurationProperties' ->> 'value' <> 'ON'; + EOQ + + mysql_flexible_servers_with_ssl_disabled_enabled_actions_enum = ["skip", "set_parameter_require_secure_transport"] + mysql_flexible_servers_with_ssl_disabled_default_action_enum = ["notify", "skip", "set_parameter_require_secure_transport"] +} + +variable "mysql_flexible_servers_with_ssl_disabled_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/MySQL" + } +} + +variable "mysql_flexible_servers_with_ssl_disabled_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/MySQL" + } +} + +variable "mysql_flexible_servers_with_ssl_disabled_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + enum = ["notify", "skip", "set_parameter_require_secure_transport"] + + tags = { + folder = "Advanced/MySQL" + } +} + +variable "mysql_flexible_servers_with_ssl_disabled_enabled_actions" { + type = list(string) + description = "The list of enabled actions to provide to approvers for selection." + default = ["skip", "set_parameter_require_secure_transport"] + enum = ["skip", "set_parameter_require_secure_transport"] + + tags = { + folder = "Advanced/MySQL" + } +} + +trigger "query" "detect_and_correct_mysql_flexible_servers_with_ssl_disabled" { + title = "Detect & correct MySQL flexible servers with SSL disabled" + description = "Detect MySQL flexible servers with SSL disabled and then enable SSL." + tags = merge(local.mysql_common_tags, { recommended = "true" }) + + enabled = var.mysql_flexible_servers_with_ssl_disabled_trigger_enabled + schedule = var.mysql_flexible_servers_with_ssl_disabled_trigger_schedule + database = var.database + sql = local.mysql_flexible_servers_with_ssl_disabled_query + + capture "insert" { + pipeline = pipeline.correct_mysql_flexible_servers_with_ssl_disabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_mysql_flexible_servers_with_ssl_disabled" { + title = "Detect & correct MySQL flexible servers with SSL disabled" + description = "Detect MySQL flexible servers with SSL disabled and then enable SSL." + tags = local.mysql_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.mysql_flexible_servers_with_ssl_disabled_default_action + enum = local.mysql_flexible_servers_with_ssl_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.mysql_flexible_servers_with_ssl_disabled_enabled_actions + enum = local.mysql_flexible_servers_with_ssl_disabled_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.mysql_flexible_servers_with_ssl_disabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_mysql_flexible_servers_with_ssl_disabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_mysql_flexible_servers_with_ssl_disabled" { + title = "Correct MySQL flexible servers with SSL disabled" + description = "Enable SSL for MySQL flexible servers with SSL disabled." + tags = merge(local.mysql_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + server_name = string + resource_group = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.mysql_flexible_servers_with_ssl_disabled_default_action + enum = local.mysql_flexible_servers_with_ssl_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.mysql_flexible_servers_with_ssl_disabled_enabled_actions + enum = local.mysql_flexible_servers_with_ssl_disabled_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} MySQL flexible server(s) with SSL disabled." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.id => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_mysql_flexible_server_with_ssl_disabled + args = { + title = each.value.title + server_name = each.value.server_name + resource_group = each.value.resource_group + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_mysql_flexible_server_with_ssl_disabled" { + title = "Correct MySQL flexible server with SSL disabled" + description = "Enable SSL for a MySQL flexible server with SSL disabled" + tags = merge(local.mysql_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "server_name" { + type = string + description = "The name of the MySQL flexible server." + } + + param "resource_group" { + type = string + description = local.description_resource_group + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.mysql_flexible_servers_with_ssl_disabled_default_action + enum = local.mysql_flexible_servers_with_ssl_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.mysql_flexible_servers_with_ssl_disabled_enabled_actions + enum = local.mysql_flexible_servers_with_ssl_disabled_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected MySQL flexible server ${param.title} with SSL disabled." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped MySQL flexible server ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "set_parameter_require_secure_transport" = { + label = "Set require secure transport parameter to 'On'" + value = "set_parameter_require_secure_transport" + style = local.style_alert + pipeline_ref = azure.pipeline.set_mysql_flexible_server_parameter + pipeline_args = { + server_name = param.server_name + resource_group = param.resource_group + subscription_id = param.subscription_id + conn = param.conn + parameter_name = "require_secure_transport" + parameter_value = "ON" + } + success_msg = "Enabled SSL for MySQL flexible server ${param.title}." + error_msg = "Error enabling SSL for MySQL flexible server ${param.title}." + } + } + } + } +} diff --git a/pipelines/mysql/mysql_flexible_servers_without_min_tls_1_2.fp b/pipelines/mysql/mysql_flexible_servers_without_min_tls_1_2.fp new file mode 100644 index 0000000..84555d8 --- /dev/null +++ b/pipelines/mysql/mysql_flexible_servers_without_min_tls_1_2.fp @@ -0,0 +1,323 @@ +locals { + mysql_flexible_servers_without_min_tls_1_2_query = <<-EOQ + select + concat(id, ' [', subscription_id, '/', resource_group, ']') as title, + id as id, + name as server_name, + resource_group, + subscription_id, + _ctx ->> 'connection_name' as conn + from + azure_mysql_flexible_server, + jsonb_array_elements(flexible_server_configurations) as config + where + config ->> 'Name' = 'tls_version' + and config -> 'ConfigurationProperties' ->> 'value' not in ('TLSv1.3', 'TLSv1.3'); + EOQ + + mysql_flexible_servers_without_min_tls_1_2_enabled_actions_enum = ["skip", "enable_min_tls_1_2"] + mysql_flexible_servers_without_min_tls_1_2_default_action_enum = ["notify", "skip", "enable_min_tls_1_2"] +} + +variable "mysql_flexible_servers_without_min_tls_1_2_trigger_enabled" { + type = bool + default = false + description = "If true, the trigger is enabled." + + tags = { + folder = "Advanced/MySQL" + } +} + +variable "mysql_flexible_servers_without_min_tls_1_2_trigger_schedule" { + type = string + default = "15m" + description = "If the trigger is enabled, run it on this schedule." + + tags = { + folder = "Advanced/MySQL" + } +} + +variable "mysql_flexible_servers_without_min_tls_1_2_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/MySQL" + } +} + +variable "mysql_flexible_servers_without_min_tls_1_2_enabled_actions" { + type = list(string) + description = "The list of enabled actions to provide to approvers for selection." + default = ["skip", "enable_min_tls_1_2"] + + tags = { + folder = "Advanced/MySQL" + } +} + +trigger "query" "detect_and_correct_mysql_flexible_servers_without_min_tls_1_2" { + title = "Detect & correct MySQL flexible servers without minimum TLS 1.2" + description = "Detect MySQL flexible servers without minimum TLS 1.2 and then enable TLS version 1.2." + tags = local.mysql_common_tags + + enabled = var.mysql_flexible_servers_without_min_tls_1_2_trigger_enabled + schedule = var.mysql_flexible_servers_without_min_tls_1_2_trigger_schedule + database = var.database + sql = local.mysql_flexible_servers_without_min_tls_1_2_query + + capture "insert" { + pipeline = pipeline.correct_mysql_flexible_servers_without_min_tls_1_2 + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_mysql_flexible_servers_without_min_tls_1_2" { + title = "Detect & correct MySQL flexible servers without minimum TLS 1.2" + description = "Detect MySQL flexible servers without minimum TLS 1.2 and then enable TLS version 1.2." + tags = local.mysql_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.mysql_flexible_servers_without_min_tls_1_2_default_action + enum = local.mysql_flexible_servers_without_min_tls_1_2_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.mysql_flexible_servers_without_min_tls_1_2_enabled_actions + enum = local.mysql_flexible_servers_without_min_tls_1_2_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.mysql_flexible_servers_without_min_tls_1_2_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_mysql_flexible_servers_without_min_tls_1_2 + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_mysql_flexible_servers_without_min_tls_1_2" { + title = "Correct MySQL flexible servers without minimum TLS 1.2" + description = "Enable 1.2 TLS version for MySQL flexible servers with minimum TLS version less than 1.2." + tags = merge(local.mysql_common_tags, { folder = "Internal" }) + + + param "items" { + type = list(object({ + id = string + title = string + server_name = string + resource_group = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.mysql_flexible_servers_without_min_tls_1_2_default_action + enum = local.mysql_flexible_servers_without_min_tls_1_2_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.mysql_flexible_servers_without_min_tls_1_2_enabled_actions + enum = local.mysql_flexible_servers_without_min_tls_1_2_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} MySQL flexible server(s) with minimum TLS version less than 1.2." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.title => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_mysql_flexible_server_with_no_min_tls_1_2 + args = { + title = each.value.title + server_name = each.value.server_name + resource_group = each.value.resource_group + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_mysql_flexible_server_with_no_min_tls_1_2" { + title = "Correct MySQL flexible server without minimum TLS 1.2" + description = "Enable 1.2 TLS version for a MySQL flexible server with minimum TLS version less than 1.2." + tags = merge(local.mysql_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "server_name" { + type = string + description = "The name of the MySQL flexible server." + } + + param "resource_group" { + type = string + description = local.description_resource_group + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.mysql_flexible_servers_without_min_tls_1_2_default_action + enum = local.mysql_flexible_servers_without_min_tls_1_2_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.mysql_flexible_servers_without_min_tls_1_2_enabled_actions + enum = local.mysql_flexible_servers_without_min_tls_1_2_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected MySQL flexible server ${param.title} with minimum TLS version less than 1.2." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped MySQL flexible server ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "enable_min_tls_1_2" = { + label = "Enable minimum TLS 1.2" + value = "enable_min_tls_1_2" + style = local.style_alert + pipeline_ref = azure.pipeline.set_mysql_flexible_server_parameter + pipeline_args = { + resource_group = param.resource_group + subscription_id = param.subscription_id + server_name = param.server_name + conn = param.conn + parameter_name = "tls_version" + parameter_value = "TLSv1.2" + } + success_msg = "Enabled minimum TLS 1.2 for MySQL flexible server ${param.title}." + error_msg = "Error enabling minimum TLS 1.2 for MySQL flexible server ${param.title}." + } + } + } + } +} diff --git a/pipelines/network/network.fp b/pipelines/network/network.fp new file mode 100644 index 0000000..b5d8901 --- /dev/null +++ b/pipelines/network/network.fp @@ -0,0 +1,5 @@ +locals { + network_common_tags = merge(local.azure_compliance_common_tags, { + service = "Azure/Network" + }) +} diff --git a/pipelines/network/network_lbs_with_basic_sku.fp b/pipelines/network/network_lbs_with_basic_sku.fp new file mode 100644 index 0000000..60c5dc8 --- /dev/null +++ b/pipelines/network/network_lbs_with_basic_sku.fp @@ -0,0 +1,132 @@ +locals { + network_lbs_with_basic_sku_query = <<-EOQ + select + concat(id, ' [', subscription_id, '/', resource_group, ']') as title, + id as id, + name, + resource_group, + subscription_id, + _ctx ->> 'connection_name' as conn + from + azure_lb + where + sku_name = 'Basic'; + EOQ +} + +variable "network_lbs_with_basic_sku_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/Network" + } +} + +variable "network_lbs_with_basic_sku_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/Network" + } +} + +trigger "query" "detect_and_correct_network_lbs_with_basic_sku" { + title = "Detect & correct Network load balancers with basic SKU" + description = "Detect Network load balancers with basic SKU." + tags = local.network_common_tags + + enabled = var.network_lbs_with_basic_sku_trigger_enabled + schedule = var.network_lbs_with_basic_sku_trigger_schedule + database = var.database + sql = local.network_lbs_with_basic_sku_query + + capture "insert" { + pipeline = pipeline.correct_network_lbs_with_basic_sku + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_network_lbs_with_basic_sku" { + title = "Detect & correct Network load balancers with basic SKU" + description = "Detect Network load balancers with basic SKU." + tags = local.network_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.network_lbs_with_basic_sku_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_network_lbs_with_basic_sku + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_network_lbs_with_basic_sku" { + title = "Correct Network load balancer using basic SKU" + description = "Send notifications for a Network load balancer using basic SKU." + tags = merge(local.network_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} Network load balancer(s) using basic SKU." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected Network load balancer ${each.value.title} using basic SKU." + } +} diff --git a/pipelines/network/network_public_ips_with_basic_sku.fp b/pipelines/network/network_public_ips_with_basic_sku.fp new file mode 100644 index 0000000..092e354 --- /dev/null +++ b/pipelines/network/network_public_ips_with_basic_sku.fp @@ -0,0 +1,132 @@ +locals { + network_public_ips_with_basic_sku_query = <<-EOQ + select + concat(id, ' [', subscription_id, '/', resource_group, ']') as title, + id as id, + name, + resource_group, + subscription_id, + _ctx ->> 'connection_name' as conn + from + azure_public_ip + where + sku_name = 'Basic'; + EOQ +} + +variable "network_public_ips_with_basic_sku_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/Network" + } +} + +variable "network_public_ips_with_basic_sku_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/Network" + } +} + +trigger "query" "detect_and_correct_network_public_ips_with_basic_sku" { + title = "Detect & correct Network public IPs with basic SKU" + description = "Detect Network public IPs with basic SKU." + tags = local.network_common_tags + + enabled = var.network_public_ips_with_basic_sku_trigger_enabled + schedule = var.network_public_ips_with_basic_sku_trigger_schedule + database = var.database + sql = local.network_public_ips_with_basic_sku_query + + capture "insert" { + pipeline = pipeline.correct_network_public_ips_with_basic_sku + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_network_public_ips_with_basic_sku" { + title = "Detect & correct Network public IPs with basic SKU" + description = "Detect Network public IPs with basic SKU." + tags = local.network_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.network_public_ips_with_basic_sku_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_network_public_ips_with_basic_sku + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_network_public_ips_with_basic_sku" { + title = "Correct Network public IP using basic SKU" + description = "Send notifications for Network public using basic SKU." + tags = merge(local.network_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} Network public IP(s) using basic SKU." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected Network public IP ${each.value.title} using basic SKU." + } +} diff --git a/pipelines/network/network_security_groups_allowing_inbound_to_https_port.fp b/pipelines/network/network_security_groups_allowing_inbound_to_https_port.fp new file mode 100644 index 0000000..fe3aec2 --- /dev/null +++ b/pipelines/network/network_security_groups_allowing_inbound_to_https_port.fp @@ -0,0 +1,372 @@ +locals { + network_security_groups_allowing_inbound_to_https_port_query = <<-EOQ + select + concat(nsg.id, ' [', nsg.subscription_id, '/', nsg.resource_group, '/', sg ->> 'name', ']') as title, + sg ->> 'name' as rule_name, + nsg.name as sg_name, + sip as source_address, + dport as destination_port, + nsg.resource_group, + nsg.subscription_id, + nsg._ctx ->> 'connection_name' as conn + from + azure_network_security_group nsg, + jsonb_array_elements(security_rules) sg, + jsonb_array_elements_text( + sg -> 'properties' -> 'destinationPortRanges' || (sg -> 'properties' -> 'destinationPortRange') :: jsonb + ) dport, + jsonb_array_elements_text( + sg -> 'properties' -> 'sourceAddressPrefixes' || (sg -> 'properties' -> 'sourceAddressPrefix') :: jsonb + ) sip + where + sg -> 'properties' ->> 'access' = 'Allow' + and sg -> 'properties' ->> 'direction' = 'Inbound' + and sg -> 'properties' ->> 'protocol' ilike 'TCP' + and sip in ( + '*', + '0.0.0.0', + '0.0.0.0/0', + 'Internet', + 'any', + '/0', + '/0' + ) + and ( + dport in ('80', '443', '*') + or ( + dport like '%-%' + and split_part(dport, '-', 1) :: integer <= 80 + and split_part(dport, '-', 2) :: integer >= 80 + ) + or ( + dport like '%-%' + and split_part(dport, '-', 1) :: integer <= 443 + and split_part(dport, '-', 2) :: integer >= 443 + ) + ) + EOQ + + network_security_groups_allowing_inbound_to_https_port_enabled_actions_enum = ["skip", "revoke_nsg_rule"] + network_security_groups_allowing_inbound_to_https_port_default_action_enum = ["notify", "skip", "revoke_nsg_rule"] +} + +variable "network_security_groups_allowing_inbound_to_https_port_trigger_enabled" { + type = bool + default = false + description = "If true, the trigger is enabled." + + tags = { + folder = "Advanced/Network" + } +} + +variable "network_security_groups_allowing_inbound_to_https_port_trigger_schedule" { + type = string + default = "15m" + description = "If the trigger is enabled, run it on this schedule." + + tags = { + folder = "Advanced/Network" + } +} + +variable "network_security_groups_allowing_inbound_to_https_port_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/Network" + } +} + +variable "network_security_groups_allowing_inbound_to_https_port_enabled_actions" { + type = list(string) + description = "The list of enabled actions to provide to approvers for selection." + default = ["skip", "revoke_nsg_rule"] + + tags = { + folder = "Advanced/Network" + } +} + +trigger "query" "detect_and_correct_network_security_groups_allowing_inbound_to_https_port" { + title = "Detect & correct NSGs allowing inbound to HTTPS port" + description = "Detect NSGs that allow inbound from 0.0.0.0/0 to HTTPS port and then revoke NSG rule." + tags = local.network_common_tags + + enabled = var.network_security_groups_allowing_inbound_to_https_port_trigger_enabled + schedule = var.network_security_groups_allowing_inbound_to_https_port_trigger_schedule + database = var.database + sql = local.network_security_groups_allowing_inbound_to_https_port_query + + capture "insert" { + pipeline = pipeline.correct_network_security_groups_allowing_inbound_to_https_port + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_network_security_groups_allowing_inbound_to_https_port" { + title = "Detect & correct NSGs allowing inbound to HTTPS port" + description = "Detect NSGs that allow inbound from 0.0.0.0/0 to HTTPS port and then revoke NSG rule." + tags = local.network_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.network_security_groups_allowing_inbound_to_https_port_default_action + enum = local.network_security_groups_allowing_inbound_to_https_port_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.network_security_groups_allowing_inbound_to_https_port_enabled_actions + enum = local.network_security_groups_allowing_inbound_to_https_port_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.network_security_groups_allowing_inbound_to_https_port_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_network_security_groups_allowing_inbound_to_https_port + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_network_security_groups_allowing_inbound_to_https_port" { + title = "Correct NSGs allowing inbound to HTTPS port" + description = "Revoke NSG rule entries to restrict access to HTTPS port from 0.0.0.0/0." + tags = merge(local.network_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + rule_name = string + sg_name = string + destination_port = string + source_address = string + resource_group = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.network_security_groups_allowing_inbound_to_https_port_default_action + enum = local.network_security_groups_allowing_inbound_to_https_port_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.network_security_groups_allowing_inbound_to_https_port_enabled_actions + enum = local.network_security_groups_allowing_inbound_to_https_port_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} NSG rule(s) allowing inbound to HTTPS port from 0.0.0.0/0." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.title => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_network_security_group_allowing_inbound_to_https_port + args = { + title = each.value.title + rule_name = each.value.rule_name + sg_name = each.value.sg_name + destination_port = each.value.destination_port + source_address = each.value.source_address + resource_group = each.value.resource_group + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_network_security_group_allowing_inbound_to_https_port" { + title = "Correct one NSG allowing inbound to HTTPS port" + description = "Revoke a NSG rule allowing ingress to HTTPS port from 0.0.0.0/0." + tags = merge(local.network_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "resource_group" { + type = string + description = local.description_resource_group + } + + param "rule_name" { + type = string + description = "The name of NSG rule." + } + + param "sg_name" { + type = string + description = "The name of NSG." + } + + param "destination_port" { + type = string + description = "The destination port." + } + + param "source_address" { + type = string + description = "The source address." + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.network_security_groups_allowing_inbound_to_https_port_default_action + enum = local.network_security_groups_allowing_inbound_to_https_port_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.network_security_groups_allowing_inbound_to_https_port_enabled_actions + enum = local.network_security_groups_allowing_inbound_to_https_port_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected NSG rule ${param.rule_name} in ${param.sg_name}/${param.subscription_id} allowing inbound on HTTPS and port(s) ${param.destination_port} from ${param.source_address}." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped NSG rule ${param.rule_name} in ${param.sg_name}/${param.subscription_id}." + } + success_msg = "" + error_msg = "" + }, + "revoke_nsg_rule" = { + label = "Delete HTTPS NSG Rule" + value = "revoke_nsg_rule" + style = local.style_alert + pipeline_ref = azure.pipeline.delete_network_nsg_rule + pipeline_args = { + resource_group = param.resource_group + nsg_name = param.sg_name + nsg_rule_name = param.rule_name + subscription_id = param.subscription_id + conn = param.conn + } + success_msg = "Revoked NSG rule ${param.rule_name} from ${param.sg_name}/${param.subscription_id}." + error_msg = "Error revoking NSG inbound rule ${param.rule_name} from security group ${param.sg_name}/${param.subscription_id}." + } + } + } + } +} diff --git a/pipelines/network/network_security_groups_allowing_inbound_to_rdp_port.fp b/pipelines/network/network_security_groups_allowing_inbound_to_rdp_port.fp new file mode 100644 index 0000000..a17977e --- /dev/null +++ b/pipelines/network/network_security_groups_allowing_inbound_to_rdp_port.fp @@ -0,0 +1,372 @@ +locals { + network_security_groups_allowing_inbound_to_rdp_port_query = <<-EOQ + select + concat(nsg.id, ' [', nsg.subscription_id, '/', nsg.resource_group, '/', sg ->> 'name', ']') as title, + sg ->> 'name' as rule_name, + sip as source_address, + dport as destination_port, + nsg.name as sg_name, + nsg.resource_group, + nsg.subscription_id, + nsg._ctx ->> 'connection_name' as conn + from + azure_network_security_group nsg, + jsonb_array_elements(security_rules) sg, + jsonb_array_elements_text( + sg -> 'properties' -> 'destinationPortRanges' || (sg -> 'properties' -> 'destinationPortRange') :: jsonb + ) dport, + jsonb_array_elements_text( + sg -> 'properties' -> 'sourceAddressPrefixes' || (sg -> 'properties' -> 'sourceAddressPrefix') :: jsonb + ) sip + where + sg -> 'properties' ->> 'access' = 'Allow' + and sg -> 'properties' ->> 'direction' = 'Inbound' + and ( + sg -> 'properties' ->> 'protocol' ilike 'TCP' + or sg -> 'properties' ->> 'protocol' = '*' + ) + and sip in ( + '*', + '0.0.0.0', + '0.0.0.0/0', + 'Internet', + 'any', + '/0', + '/0' + ) + and ( + dport in ('3389', '*') + or ( + dport like '%-%' + and split_part(dport, '-', 1) :: integer <= 3389 + and split_part(dport, '-', 2) :: integer >= 3389 + ) + ) + EOQ + + network_security_groups_allowing_inbound_to_rdp_port_enabled_actions_enum = ["skip", "revoke_nsg_rule"] + network_security_groups_allowing_inbound_to_rdp_port_default_action_enum = ["notify", "skip", "revoke_nsg_rule"] +} + +variable "network_security_groups_allowing_inbound_to_rdp_port_trigger_enabled" { + type = bool + default = false + description = "If true, the trigger is enabled." + + tags = { + folder = "Advanced/Network" + } +} + +variable "network_security_groups_allowing_inbound_to_rdp_port_trigger_schedule" { + type = string + default = "15m" + description = "If the trigger is enabled, run it on this schedule." + + tags = { + folder = "Advanced/Network" + } +} + +variable "network_security_groups_allowing_inbound_to_rdp_port_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/Network" + } +} + +variable "network_security_groups_allowing_inbound_to_rdp_port_enabled_actions" { + type = list(string) + description = "The list of enabled actions to provide to approvers for selection." + default = ["skip", "revoke_nsg_rule"] + + tags = { + folder = "Advanced/Network" + } +} + +trigger "query" "detect_and_correct_network_security_groups_allowing_inbound_to_rdp_port" { + title = "Detect & correct NSGs allowing inbound to RDP port" + description = "Detect NSGs that allow inbound from 0.0.0.0/0 to RDP port and then revoke NSG rule." + tags = local.network_common_tags + + enabled = var.network_security_groups_allowing_inbound_to_rdp_port_trigger_enabled + schedule = var.network_security_groups_allowing_inbound_to_rdp_port_trigger_schedule + database = var.database + sql = local.network_security_groups_allowing_inbound_to_rdp_port_query + + capture "insert" { + pipeline = pipeline.correct_network_security_groups_allowing_inbound_to_rdp_port + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_network_security_groups_allowing_inbound_to_rdp_port" { + title = "Detect & correct NSGs allowing inbound to RDP port" + description = "Detect NSGs that allow inbound from 0.0.0.0/0 to RDP port and then revoke NSG rule." + tags = local.network_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.network_security_groups_allowing_inbound_to_rdp_port_default_action + enum = local.network_security_groups_allowing_inbound_to_rdp_port_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.network_security_groups_allowing_inbound_to_rdp_port_enabled_actions + enum = local.network_security_groups_allowing_inbound_to_rdp_port_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.network_security_groups_allowing_inbound_to_rdp_port_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_network_security_groups_allowing_inbound_to_rdp_port + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_network_security_groups_allowing_inbound_to_rdp_port" { + title = "Correct NSGs allowing inbound to RDP port" + description = "Revoke NSG rule entries to restrict access to RDP port from 0.0.0.0/0." + tags = merge(local.network_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + rule_name = string + sg_name = string + destination_port = string + source_address = string + resource_group = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.network_security_groups_allowing_inbound_to_rdp_port_default_action + enum = local.network_security_groups_allowing_inbound_to_rdp_port_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.network_security_groups_allowing_inbound_to_rdp_port_enabled_actions + enum = local.network_security_groups_allowing_inbound_to_rdp_port_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} NSG rule(s) allowing inbound to RDP port from 0.0.0.0/0." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.title => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_network_security_group_allowing_inbound_to_rdp_port + args = { + title = each.value.title + rule_name = each.value.rule_name + sg_name = each.value.sg_name + destination_port = each.value.destination_port + source_address = each.value.source_address + resource_group = each.value.resource_group + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_network_security_group_allowing_inbound_to_rdp_port" { + title = "Correct one NSG allowing inbound to RDP port" + description = "Revoke a NSG rule allowing ingress to RDP port from 0.0.0.0/0." + tags = merge(local.network_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "resource_group" { + type = string + description = local.description_resource_group + } + + param "rule_name" { + type = string + description = "The name of NSG rule." + } + + param "sg_name" { + type = string + description = "The name of NSG." + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "destination_port" { + type = string + description = "The destination port." + } + + param "source_address" { + type = string + description = "The source address." + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.network_security_groups_allowing_inbound_to_rdp_port_default_action + enum = local.network_security_groups_allowing_inbound_to_rdp_port_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.network_security_groups_allowing_inbound_to_rdp_port_enabled_actions + enum = local.network_security_groups_allowing_inbound_to_rdp_port_enabled_actions_enum + } + + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected NSG rule ${param.rule_name} in ${param.sg_name}/${param.subscription_id} allowing inbound on RDP and port(s) ${param.destination_port} from ${param.source_address}." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped NSG rule ${param.rule_name} in ${param.sg_name}/${param.subscription_id}." + } + success_msg = "" + error_msg = "" + }, + "revoke_nsg_rule" = { + label = "Delete NSG rule" + value = "revoke_nsg_rule" + style = local.style_alert + pipeline_ref = azure.pipeline.delete_network_nsg_rule + pipeline_args = { + resource_group = param.resource_group + nsg_name = param.sg_name + nsg_rule_name = param.rule_name + subscription_id = param.subscription_id + conn = param.conn + } + success_msg = "Revoked NSG rule ${param.rule_name} from ${param.sg_name}/${param.subscription_id}." + error_msg = "Error revoking NSG inbound rule ${param.rule_name} from security group ${param.sg_name}/${param.subscription_id}." + } + } + } + } +} + diff --git a/pipelines/network/network_security_groups_allowing_inbound_to_ssh_port.fp b/pipelines/network/network_security_groups_allowing_inbound_to_ssh_port.fp new file mode 100644 index 0000000..14dafaf --- /dev/null +++ b/pipelines/network/network_security_groups_allowing_inbound_to_ssh_port.fp @@ -0,0 +1,370 @@ +locals { + network_security_groups_allowing_inbound_to_ssh_port_query = <<-EOQ + select + concat(nsg.id, ' [', nsg.subscription_id, '/', nsg.resource_group, '/', sg ->> 'name', ']') as title, + sg ->> 'name' as rule_name, + nsg.name as sg_name, + sip as source_address, + dport as destination_port, + nsg.resource_group, + nsg.subscription_id, + nsg._ctx ->> 'connection_name' as conn + from + azure_network_security_group nsg, + jsonb_array_elements(security_rules) sg, + jsonb_array_elements_text( + sg -> 'properties' -> 'destinationPortRanges' || (sg -> 'properties' -> 'destinationPortRange') :: jsonb + ) dport, + jsonb_array_elements_text( + sg -> 'properties' -> 'sourceAddressPrefixes' || (sg -> 'properties' -> 'sourceAddressPrefix') :: jsonb + ) sip + where + sg -> 'properties' ->> 'access' = 'Allow' + and sg -> 'properties' ->> 'direction' = 'Inbound' + and ( + sg -> 'properties' ->> 'protocol' ilike 'TCP' + or sg -> 'properties' ->> 'protocol' = '*' + ) + and sip in ( + '*', + '0.0.0.0', + '0.0.0.0/0', + 'Internet', + 'any', + '/0', + '/0' + ) + and ( + dport in ('22', '*') + or ( + dport like '%-%' + and split_part(dport, '-', 1) :: integer <= 22 + and split_part(dport, '-', 2) :: integer >= 22 + ) + ) + EOQ + + network_security_groups_allowing_inbound_to_ssh_port_enabled_actions_enum = ["skip", "revoke_nsg_rule"] + network_security_groups_allowing_inbound_to_ssh_port_default_action_enum = ["notify", "skip", "revoke_nsg_rule"] +} + +variable "network_security_groups_allowing_inbound_to_ssh_port_trigger_enabled" { + type = bool + default = false + description = "If true, the trigger is enabled." + + tags = { + folder = "Advanced/Network" + } +} + +variable "network_security_groups_allowing_inbound_to_ssh_port_trigger_schedule" { + type = string + default = "15m" + description = "If the trigger is enabled, run it on this schedule." + + tags = { + folder = "Advanced/Network" + } +} + +variable "network_security_groups_allowing_inbound_to_ssh_port_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/Network" + } +} + +variable "network_security_groups_allowing_inbound_to_ssh_port_enabled_actions" { + type = list(string) + description = "The list of enabled actions to provide to approvers for selection." + default = ["skip", "revoke_nsg_rule"] + + tags = { + folder = "Advanced/Network" + } +} + +trigger "query" "detect_and_correct_network_security_groups_allowing_inbound_to_ssh_port" { + title = "Detect & correct NSGs allowing inbound to SSH port" + description = "Detect NSGs that allow inbound from 0.0.0.0/0 to SSH port and then revoke NSG rule." + tags = local.network_common_tags + + enabled = var.network_security_groups_allowing_inbound_to_ssh_port_trigger_enabled + schedule = var.network_security_groups_allowing_inbound_to_ssh_port_trigger_schedule + database = var.database + sql = local.network_security_groups_allowing_inbound_to_ssh_port_query + + capture "insert" { + pipeline = pipeline.correct_network_security_groups_allowing_inbound_to_ssh_port + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_network_security_groups_allowing_inbound_to_ssh_port" { + title = "Detect & correct NSGs allowing inbound to SSH port" + description = "Detect NSGs that allow inbound from 0.0.0.0/0 to SSH port and then revoke NSG rule." + tags = local.network_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.network_security_groups_allowing_inbound_to_ssh_port_default_action + enum = local.network_security_groups_allowing_inbound_to_ssh_port_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.network_security_groups_allowing_inbound_to_ssh_port_enabled_actions + enum = local.network_security_groups_allowing_inbound_to_ssh_port_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.network_security_groups_allowing_inbound_to_ssh_port_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_network_security_groups_allowing_inbound_to_ssh_port + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_network_security_groups_allowing_inbound_to_ssh_port" { + title = "Correct NSGs allowing inbound to SSH port" + description = "Revoke NSG rule entries to restrict access to SSH port from 0.0.0.0/0." + tags = merge(local.network_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + rule_name = string + sg_name = string + destination_port = string + source_address = string + resource_group = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.network_security_groups_allowing_inbound_to_ssh_port_default_action + enum = local.network_security_groups_allowing_inbound_to_ssh_port_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.network_security_groups_allowing_inbound_to_ssh_port_enabled_actions + enum = local.network_security_groups_allowing_inbound_to_ssh_port_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} NSG rule(s) allowing inbound to SSH port from 0.0.0.0/0." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.title => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_network_security_group_allowing_inbound_to_ssh_port + args = { + title = each.value.title + rule_name = each.value.rule_name + sg_name = each.value.sg_name + destination_port = each.value.destination_port + source_address = each.value.source_address + resource_group = each.value.resource_group + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_network_security_group_allowing_inbound_to_ssh_port" { + title = "Correct one NSG allowing inbound to SSH port" + description = "Revoke a NSG rule allowing ingress to SSH port from 0.0.0.0/0." + tags = merge(local.network_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "resource_group" { + type = string + description = local.description_resource_group + } + + param "rule_name" { + type = string + description = "The name of NSG rule." + } + + param "sg_name" { + type = string + description = "The name of NSG." + } + + param "destination_port" { + type = string + description = "The destination port." + } + + param "source_address" { + type = string + description = "The source address." + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.network_security_groups_allowing_inbound_to_ssh_port_default_action + enum = local.network_security_groups_allowing_inbound_to_ssh_port_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.network_security_groups_allowing_inbound_to_ssh_port_enabled_actions + enum = local.network_security_groups_allowing_inbound_to_ssh_port_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected NSG rule ${param.rule_name} in ${param.sg_name}/${param.subscription_id} allowing inbound on SSH and port(s) ${param.destination_port} from ${param.source_address}." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped NSG rule ${param.rule_name} in ${param.sg_name}/${param.subscription_id}." + } + success_msg = "" + error_msg = "" + }, + "revoke_nsg_rule" = { + label = "Revoke NSG rule" + value = "revoke_nsg_rule" + style = local.style_alert + pipeline_ref = azure.pipeline.delete_network_nsg_rule + pipeline_args = { + resource_group = param.resource_group + nsg_name = param.sg_name + nsg_rule_name = param.rule_name + subscription_id = param.subscription_id + conn = param.conn + } + success_msg = "Revoked NSG rule ${param.rule_name} from ${param.sg_name}/${param.subscription_id}." + error_msg = "Error revoking NSG inbound rule ${param.rule_name} from security group ${param.sg_name}/${param.subscription_id}." + } + } + } + } +} diff --git a/pipelines/network/network_security_groups_allowing_inbound_to_udp_port.fp b/pipelines/network/network_security_groups_allowing_inbound_to_udp_port.fp new file mode 100644 index 0000000..ee801c3 --- /dev/null +++ b/pipelines/network/network_security_groups_allowing_inbound_to_udp_port.fp @@ -0,0 +1,378 @@ +locals { + network_security_groups_allowing_inbound_to_udp_port_query = <<-EOQ + select + concat(nsg.id, ' [', nsg.subscription_id, '/', nsg.resource_group, '/', sg ->> 'name', ']') as title, + sg ->> 'name' as rule_name, + nsg.name as sg_name, + nsg.resource_group, + nsg.subscription_id, + sip as source_address, + dport as destination_port, + nsg._ctx ->> 'connection_name' as conn + from + azure_network_security_group nsg, + jsonb_array_elements(security_rules) sg, + jsonb_array_elements_text( + sg -> 'properties' -> 'destinationPortRanges' || (sg -> 'properties' -> 'destinationPortRange') :: jsonb + ) dport, + jsonb_array_elements_text( + sg -> 'properties' -> 'sourceAddressPrefixes' || (sg -> 'properties' -> 'sourceAddressPrefix') :: jsonb + ) sip + where + sg -> 'properties' ->> 'access' = 'Allow' + and sg -> 'properties' ->> 'direction' = 'Inbound' + and sg -> 'properties' ->> 'protocol' = 'UDP' + and sip in ( + '*', + '0.0.0.0', + '0.0.0.0/0', + 'Internet', + 'any', + '/0', + '/0' + ) + and ( + dport = '*' + or ( + dport like '%-%' + and ( + 53 between split_part(dport, '-', 1) :: integer + and split_part(dport, '-', 2) :: integer + or 123 between split_part(dport, '-', 1) :: integer + and split_part(dport, '-', 2) :: integer + or 161 between split_part(dport, '-', 1) :: integer + and split_part(dport, '-', 2) :: integer + or 389 between split_part(dport, '-', 1) :: integer + and split_part(dport, '-', 2) :: integer + or 1900 between split_part(dport, '-', 1) :: integer + and split_part(dport, '-', 2) :: integer + ) + ) + ) + EOQ + + network_security_groups_allowing_inbound_to_udp_port_enabled_actions_enum = ["skip", "revoke_nsg_rule"] + network_security_groups_allowing_inbound_to_udp_port_default_action_enum = ["notify", "skip", "revoke_nsg_rule"] +} + +variable "network_security_groups_allowing_inbound_to_udp_port_trigger_enabled" { + type = bool + default = false + description = "If true, the trigger is enabled." + + tags = { + folder = "Advanced/Network" + } +} + +variable "network_security_groups_allowing_inbound_to_udp_port_trigger_schedule" { + type = string + default = "15m" + description = "If the trigger is enabled, run it on this schedule." + + tags = { + folder = "Advanced/Network" + } +} + +variable "network_security_groups_allowing_inbound_to_udp_port_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/Network" + } +} + +variable "network_security_groups_allowing_inbound_to_udp_port_enabled_actions" { + type = list(string) + description = "The list of enabled actions to provide to approvers for selection." + default = ["skip", "revoke_nsg_rule"] + + tags = { + folder = "Advanced/Network" + } +} + +trigger "query" "detect_and_correct_network_security_groups_allowing_inbound_to_udp_port" { + title = "Detect & correct NSGs allowing inbound to UDP port" + description = "Detect NSGs that allow inbound from 0.0.0.0/0 to UDP port and then revoke NSG rule." + tags = local.network_common_tags + + enabled = var.network_security_groups_allowing_inbound_to_udp_port_trigger_enabled + schedule = var.network_security_groups_allowing_inbound_to_udp_port_trigger_schedule + database = var.database + sql = local.network_security_groups_allowing_inbound_to_udp_port_query + + capture "insert" { + pipeline = pipeline.correct_network_security_groups_allowing_inbound_to_udp_port + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_network_security_groups_allowing_inbound_to_udp_port" { + title = "Detect & correct NSGs allowing inbound to UDP port" + description = "Detect NSGs that allow inbound from 0.0.0.0/0 to UDP port and then revoke NSG rule." + tags = local.network_common_tags + + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.network_security_groups_allowing_inbound_to_udp_port_default_action + enum = local.network_security_groups_allowing_inbound_to_udp_port_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.network_security_groups_allowing_inbound_to_udp_port_enabled_actions + enum = local.network_security_groups_allowing_inbound_to_udp_port_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.network_security_groups_allowing_inbound_to_udp_port_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_network_security_groups_allowing_inbound_to_udp_port + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_network_security_groups_allowing_inbound_to_udp_port" { + title = "Correct NSGs allowing inbound to UDP port" + description = "Revoke NSG rule entries to restrict access to UDP port from 0.0.0.0/0." + tags = merge(local.network_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + rule_name = string + sg_name = string + destination_port = string + source_address = string + resource_group = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.network_security_groups_allowing_inbound_to_udp_port_default_action + enum = local.network_security_groups_allowing_inbound_to_udp_port_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.network_security_groups_allowing_inbound_to_udp_port_enabled_actions + enum = local.network_security_groups_allowing_inbound_to_udp_port_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} NSG rule(s) allowing inbound to UDP port from 0.0.0.0/0." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.title => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_network_security_group_allowing_inbound_to_udp_port + args = { + title = each.value.title + rule_name = each.value.rule_name + sg_name = each.value.sg_name + destination_port = each.value.destination_port + source_address = each.value.source_address + resource_group = each.value.resource_group + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_network_security_group_allowing_inbound_to_udp_port" { + title = "Correct one NSG allowing inbound to UDP port" + description = "Revoke a NSG rule allowing ingress to UDP port from 0.0.0.0/0." + tags = merge(local.network_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "resource_group" { + type = string + description = local.description_resource_group + } + + param "rule_name" { + type = string + description = "The name of NSG rule." + } + + param "sg_name" { + type = string + description = "The name of NSG." + } + + param "destination_port" { + type = string + description = "The destination port." + } + + param "source_address" { + type = string + description = "The source address." + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.network_security_groups_allowing_inbound_to_udp_port_default_action + enum = local.network_security_groups_allowing_inbound_to_udp_port_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.network_security_groups_allowing_inbound_to_udp_port_enabled_actions + enum = local.network_security_groups_allowing_inbound_to_udp_port_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected NSG rule ${param.rule_name} in ${param.sg_name}/${param.subscription_id} allowing inbound on UDP and port(s) ${param.destination_port} from ${param.source_address}." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped NSG rule ${param.rule_name} in ${param.sg_name}/${param.subscription_id}." + } + success_msg = "" + error_msg = "" + }, + "revoke_nsg_rule" = { + label = "Revoke NSG rule" + value = "revoke_nsg_rule" + style = local.style_alert + pipeline_ref = azure.pipeline.delete_network_nsg_rule + pipeline_args = { + resource_group = param.resource_group + nsg_name = param.sg_name + nsg_rule_name = param.rule_name + subscription_id = param.subscription_id + conn = param.conn + } + success_msg = "Revoked NSG rule ${param.rule_name} from ${param.sg_name}/${param.subscription_id}." + error_msg = "Error revoking NSG inbound rule ${param.rule_name} from security group ${param.sg_name}/${param.subscription_id}." + } + } + } + } +} diff --git a/pipelines/network/network_security_groups_flow_log_with_retention_period_less_than_90_days.fp b/pipelines/network/network_security_groups_flow_log_with_retention_period_less_than_90_days.fp new file mode 100644 index 0000000..b5a1fb6 --- /dev/null +++ b/pipelines/network/network_security_groups_flow_log_with_retention_period_less_than_90_days.fp @@ -0,0 +1,133 @@ +locals { + network_security_groups_flow_log_with_retention_period_less_than_90_days_query = <<-EOQ + select + concat(sg.id, ' [', sg.subscription_id, '/', sg.resource_group, ']') as title, + sg.id as id, + sg.name, + sg.resource_group, + sg.subscription_id, + sg._ctx ->> 'connection_name' as conn + from + azure_network_security_group sg + left join azure_network_watcher_flow_log fl on sg.id = fl.target_resource_id + where + fl.id is null or not fl.enabled or fl.retention_policy_days < 90 + EOQ +} + +variable "network_security_groups_flow_log_with_retention_period_less_than_90_days_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/Network" + } +} + +variable "network_security_groups_flow_log_with_retention_period_less_than_90_days_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/Network" + } +} + +trigger "query" "detect_and_correct_network_security_groups_flow_log_with_retention_period_less_than_90_days" { + title = "Detect & correct NSGs flow log with retention period less than 90 days" + description = "Detect NSGs flow log with retention period less than 90 days." + tags = local.network_common_tags + + enabled = var.network_security_groups_flow_log_with_retention_period_less_than_90_days_trigger_enabled + schedule = var.network_security_groups_flow_log_with_retention_period_less_than_90_days_trigger_schedule + database = var.database + sql = local.network_security_groups_flow_log_with_retention_period_less_than_90_days_query + + capture "insert" { + pipeline = pipeline.correct_network_security_groups_flow_log_with_retention_period_less_than_90_days + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_network_security_groups_flow_log_with_retention_period_less_than_90_days" { + title = "Detect & correct NSGs flow log with retention period less than 90 days" + description = "Detect NSGs flow log with retention period less than 90 days." + tags = local.network_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.network_security_groups_flow_log_with_retention_period_less_than_90_days_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_network_security_groups_flow_log_with_retention_period_less_than_90_days + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_network_security_groups_flow_log_with_retention_period_less_than_90_days" { + title = "Correct NSGs flow log with retention period less than 90 days" + description = "Send notifications for NSGs flow log with retention period less than 90 days." + tags = merge(local.network_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} NSG(s) flow log with retention period less than 90 days." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected NSG ${each.value.title} flow log with retention period less than 90 days." + } +} diff --git a/pipelines/network/network_watcher_disabled_in_regions.fp b/pipelines/network/network_watcher_disabled_in_regions.fp new file mode 100644 index 0000000..e1204f6 --- /dev/null +++ b/pipelines/network/network_watcher_disabled_in_regions.fp @@ -0,0 +1,139 @@ +locals { + network_watcher_disabled_in_regions_query = <<-EOQ + select + concat(loc.name, ' [', loc.subscription_id, ']') as title, + loc.id as id, + loc.name as region, + concat(loc.name, 'NetworkWatcherRG') as resource_group, + loc.subscription_id, + loc._ctx ->> 'connection_name' as conn + from + azure_location loc + left join azure_network_watcher watcher on watcher.region = loc.name + left join azure_subscription sub on sub.subscription_id = loc.subscription_id + where + watcher.id is null; + EOQ + +} + +variable "network_watcher_disabled_in_regions_trigger_enabled" { + type = bool + default = false + description = "If true, the trigger is enabled." + + tags = { + folder = "Advanced/Network" + } +} + +variable "network_watcher_disabled_in_regions_trigger_schedule" { + type = string + default = "15m" + description = "If the trigger is enabled, run it on this schedule." + + tags = { + folder = "Advanced/Network" + } +} + +trigger "query" "detect_and_correct_network_watcher_disabled_in_regions" { + title = "Detect & correct regions with network watcher disabled" + description = "Detect regions with network watcher disabled and then enable them." + tags = local.network_common_tags + + enabled = var.network_watcher_disabled_in_regions_trigger_enabled + schedule = var.network_watcher_disabled_in_regions_trigger_schedule + database = var.database + sql = local.network_watcher_disabled_in_regions_query + + capture "insert" { + pipeline = pipeline.correct_network_watcher_disabled_in_regions + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_network_watcher_disabled_in_regions" { + title = "Detect & correct regions with network watcher disabled" + description = "Detect regions with network watcher disabled and then enable them." + tags = local.network_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.network_watcher_disabled_in_regions_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_network_watcher_disabled_in_regions + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_network_watcher_disabled_in_regions" { + title = "Correct regions with network watcher disabled" + description = "Enable network watcher in regions with network watcher disabled." + tags = merge(local.network_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + region = string + subscription_id = string + resource_group = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} region(s) with network watcher disabled." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected region ${each.value.title} with network watcher disabled.." + } +} diff --git a/pipelines/network/subscriptions_without_network_bastion_host.fp b/pipelines/network/subscriptions_without_network_bastion_host.fp new file mode 100644 index 0000000..b56f30c --- /dev/null +++ b/pipelines/network/subscriptions_without_network_bastion_host.fp @@ -0,0 +1,144 @@ +locals { + subscriptions_without_network_bastion_host_query = <<-EOQ + with bastion_hosts as ( + select + subscription_id, + _ctx, + region, + resource_group, + count(*) as no_bastion_host + from + azure_bastion_host + group by + subscription_id, + _ctx, + resource_group, + region + ) + select + sub.subscription_id as title, + sub._ctx ->> 'connection_name' as conn + from + azure_subscription as sub + left join bastion_hosts as i on i.subscription_id = sub.subscription_id + where + i.subscription_id is null; + EOQ +} + +variable "subscriptions_without_network_bastion_host_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/Network" + } +} + +variable "subscriptions_without_network_bastion_host_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/Network" + } +} + +trigger "query" "detect_and_correct_subscriptions_without_network_bastion_host" { + title = "Detect & correct Subscriptions without network bastion host" + description = "Detect subscriptions without network bastion host." + tags = local.network_common_tags + + enabled = var.subscriptions_without_network_bastion_host_trigger_enabled + schedule = var.subscriptions_without_network_bastion_host_trigger_schedule + database = var.database + sql = local.subscriptions_without_network_bastion_host_query + + capture "insert" { + pipeline = pipeline.correct_subscriptions_without_network_bastion_host + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_subscriptions_without_network_bastion_host" { + title = "Detect & correct Subscriptions without network bastion host" + description = "Detect subscriptions without network bastion host." + tags = local.network_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.subscriptions_without_network_bastion_host_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_subscriptions_without_network_bastion_host + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_subscriptions_without_network_bastion_host" { + title = "Correct Subscriptions without network bastion host" + description = "Send notifications for subscriptions without network bastion host." + tags = merge(local.network_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} subscription(s) without network bastion host" + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected subscription ${each.value.title} without network bastion host." + } +} diff --git a/pipelines/postgresql/postgresql.fp b/pipelines/postgresql/postgresql.fp new file mode 100644 index 0000000..a849c51 --- /dev/null +++ b/pipelines/postgresql/postgresql.fp @@ -0,0 +1,5 @@ +locals { + postgresql_common_tags = merge(local.azure_compliance_common_tags, { + service = "Azure/PostgreSQL" + }) +} diff --git a/pipelines/postgresql/postgresql_flexible_servers_log_retention_days_less_than_or_equal_to_3_days.fp b/pipelines/postgresql/postgresql_flexible_servers_log_retention_days_less_than_or_equal_to_3_days.fp new file mode 100644 index 0000000..65ad50b --- /dev/null +++ b/pipelines/postgresql/postgresql_flexible_servers_log_retention_days_less_than_or_equal_to_3_days.fp @@ -0,0 +1,352 @@ +locals { + postgresql_flexible_servers_log_retention_days_less_than_or_equal_to_3_days_query = <<-EOQ + select + concat(id, ' [', subscription_id, '/', resource_group, ']') as title, + id as id, + name, + resource_group, + subscription_id, + _ctx ->> 'connection_name' as conn + from + azure_postgresql_flexible_server, + jsonb_array_elements(flexible_server_configurations) config + where + config ->> 'Name' = 'logfiles.retention_days' + and (config -> 'ConfigurationProperties' ->> 'value') :: integer <= 3; + EOQ + + postgresql_flexible_servers_log_retention_days_less_than_or_equal_to_3_days_enabled_actions_enum = ["skip", "update_log_retention_days"] + postgresql_flexible_servers_log_retention_days_less_than_or_equal_to_3_days_default_action_enum = ["notify", "skip", "update_log_retention_days"] +} + +variable "postgresql_flexible_servers_log_retention_days_less_than_or_equal_to_3_days_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +variable "postgresql_flexible_servers_log_retention_days_less_than_or_equal_to_3_days_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +variable "postgresql_flexible_servers_log_retention_days_less_than_or_equal_to_3_days_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +variable "postgresql_flexible_servers_log_retention_days_less_than_or_equal_to_3_days_enabled_actions" { + type = list(string) + description = "The list of enabled actions to provide to approvers for selection." + default = ["skip", "update_log_retention_days"] + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +variable "postgresql_flexible_servers_log_retention_days_less_than_or_equal_to_3_days_log_retention_days" { + type = string + description = "The number of days logs should be retained." + default = "7" + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +trigger "query" "detect_and_correct_postgresql_flexible_servers_log_retention_days_less_than_or_equal_to_3_days" { + title = "Detect & correct PostgreSQL flexible servers with log retention less than 3 days" + description = "Detect PostgreSQL flexible servers with log retention less than 3 and then sets log retention to 3 or more than 3 days." + tags = local.postgresql_common_tags + + enabled = var.postgresql_flexible_servers_log_retention_days_less_than_or_equal_to_3_days_trigger_enabled + schedule = var.postgresql_flexible_servers_log_retention_days_less_than_or_equal_to_3_days_trigger_schedule + database = var.database + sql = local.postgresql_flexible_servers_log_retention_days_less_than_or_equal_to_3_days_query + + capture "insert" { + pipeline = pipeline.correct_postgresql_flexible_servers_log_retention_days_less_than_or_equal_to_3_days + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_postgresql_flexible_servers_log_retention_days_less_than_or_equal_to_3_days" { + title = "Detect & correct PostgreSQL flexible servers with log retention less than 3 days" + description = "Detect PostgreSQL flexible servers with log retention less than 3 and then sets log retention to 3 or more than 3 days." + tags = local.postgresql_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.postgresql_flexible_servers_log_retention_days_less_than_or_equal_to_3_days_default_action + enum = local.postgresql_flexible_servers_log_retention_days_less_than_or_equal_to_3_days_default_action_enum + } + + param "log_retention_days" { + type = string + description = "The number of days logs should be retained." + default = var.postgresql_flexible_servers_log_retention_days_less_than_or_equal_to_3_days_log_retention_days + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.postgresql_flexible_servers_log_retention_days_less_than_or_equal_to_3_days_enabled_actions + enum = local.postgresql_flexible_servers_log_retention_days_less_than_or_equal_to_3_days_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.postgresql_flexible_servers_log_retention_days_less_than_or_equal_to_3_days_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_postgresql_flexible_servers_log_retention_days_less_than_or_equal_to_3_days + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + log_retention_days = param.log_retention_days + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_postgresql_flexible_servers_log_retention_days_less_than_or_equal_to_3_days" { + title = "Correct PostgreSQL flexible servers with log retention less than 3 days" + description = "Update log retention days to 3 or more for PostgreSQL flexible servers with log retention less than 3 days." + tags = merge(local.postgresql_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + name = string + resource_group = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.postgresql_flexible_servers_log_retention_days_less_than_or_equal_to_3_days_default_action + enum = local.postgresql_flexible_servers_log_retention_days_less_than_or_equal_to_3_days_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.postgresql_flexible_servers_log_retention_days_less_than_or_equal_to_3_days_enabled_actions + enum = local.postgresql_flexible_servers_log_retention_days_less_than_or_equal_to_3_days_enabled_actions_enum + } + + param "log_retention_days" { + type = string + description = "The number of days logs should be retained." + default = var.postgresql_flexible_servers_log_retention_days_less_than_or_equal_to_3_days_log_retention_days + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} PostgreSQL flexible server(s) with log retention_days less than 3 days." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.id => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_postgresql_flexible_server_with_log_retention_less_than_3_days + args = { + title = each.value.title + name = each.value.name + resource_group = each.value.resource_group + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + log_retention_days = param.log_retention_days + } + } +} + +pipeline "correct_one_postgresql_flexible_server_with_log_retention_less_than_3_days" { + title = "Correct PostgreSQL flexible server with log retention less than 3 days" + description = "Update log retention days to 3 or more for a PostgreSQL flexible server with log retention less than 3 days." + tags = merge(local.postgresql_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "name" { + type = string + description = "The name of the PostgreSQL flexible server." + } + + param "resource_group" { + type = string + description = local.description_resource_group + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.postgresql_flexible_servers_log_retention_days_less_than_or_equal_to_3_days_default_action + enum = local.postgresql_flexible_servers_log_retention_days_less_than_or_equal_to_3_days_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.postgresql_flexible_servers_log_retention_days_less_than_or_equal_to_3_days_enabled_actions + enum = local.postgresql_flexible_servers_log_retention_days_less_than_or_equal_to_3_days_enabled_actions_enum + } + + param "log_retention_days" { + type = string + description = "The number of days logs should be retained." + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected PostgreSQL flexible server ${param.title} with log retention less than or equal to 3 days." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped PostgreSQL flexible server ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "update_log_retention_days" = { + label = "Update log retention days" + value = "update_log_retention_days" + style = local.style_alert + pipeline_ref = azure.pipeline.set_postgres_flexible_server_configuration + pipeline_args = { + server_name = param.name + resource_group = param.resource_group + subscription_id = param.subscription_id + conn = param.conn + config_name = "logfiles.retention_days" + config_value = param.log_retention_days + } + success_msg = "Updated log retention days for PostgreSQL flexible server ${param.title}." + error_msg = "Error updating log retention days for PostgreSQL flexible server ${param.title}." + } + } + } + } +} + diff --git a/pipelines/postgresql/postgresql_flexible_servers_with_connection_throttling_disabled.fp b/pipelines/postgresql/postgresql_flexible_servers_with_connection_throttling_disabled.fp new file mode 100644 index 0000000..eeb5df8 --- /dev/null +++ b/pipelines/postgresql/postgresql_flexible_servers_with_connection_throttling_disabled.fp @@ -0,0 +1,324 @@ +locals { + postgresql_flexible_servers_with_connection_throttling_disabled_query = <<-EOQ + select + concat(id, ' [', subscription_id, '/', resource_group, ']') as title, + id as id, + name, + resource_group, + subscription_id, + _ctx ->> 'connection_name' as conn + from + azure_postgresql_flexible_server as db, + jsonb_array_elements(flexible_server_configurations) config + where + config ->> 'Name' = 'connection_throttle.enable' + and lower(config -> 'ConfigurationProperties' ->> 'value') != 'on' + EOQ + + postgresql_flexible_servers_with_connection_throttling_disabled_enabled_actions_enum = ["skip", "enable_connection_throttling"] + postgresql_flexible_servers_with_connection_throttling_disabled_default_action_enum = ["notify", "skip", "enable_connection_throttling"] +} + +variable "postgresql_flexible_servers_with_connection_throttling_disabled_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +variable "postgresql_flexible_servers_with_connection_throttling_disabled_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +variable "postgresql_flexible_servers_with_connection_throttling_disabled_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +variable "postgresql_flexible_servers_with_connection_throttling_disabled_enabled_actions" { + type = list(string) + description = "The list of enabled actions to provide to approvers for selection." + default = ["skip", "enable_connection_throttling"] + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +trigger "query" "detect_and_correct_postgresql_flexible_servers_with_connection_throttling_disabled" { + title = "Detect & correct PostgreSQL flexible servers with connection throttling disabled" + description = "Detect PostgreSQL flexible servers with connection throttling disabled and then enable connection throttling." + tags = local.postgresql_common_tags + + enabled = var.postgresql_flexible_servers_with_connection_throttling_disabled_trigger_enabled + schedule = var.postgresql_flexible_servers_with_connection_throttling_disabled_trigger_schedule + database = var.database + sql = local.postgresql_flexible_servers_with_connection_throttling_disabled_query + + capture "insert" { + pipeline = pipeline.correct_postgresql_flexible_servers_with_connection_throttling_disabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_postgresql_flexible_servers_with_connection_throttling_disabled" { + title = "Detect & correct PostgreSQL flexible servers with connection throttling disabled" + description = "Detect PostgreSQL flexible servers with connection throttling disabled and then enable connection throttling" + tags = merge(local.postgresql_common_tags, { recommended = "true" }) + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.postgresql_flexible_servers_with_connection_throttling_disabled_default_action + enum = local.postgresql_flexible_servers_with_connection_throttling_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.postgresql_flexible_servers_with_connection_throttling_disabled_enabled_actions + enum = local.postgresql_flexible_servers_with_connection_throttling_disabled_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.postgresql_flexible_servers_with_connection_throttling_disabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_postgresql_flexible_servers_with_connection_throttling_disabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_postgresql_flexible_servers_with_connection_throttling_disabled" { + title = "Correct PostgreSQL flexible servers with connection throttling disabled" + description = "Enable connection throttling for PostgreSQL flexible servers with connection throttling disabled." + tags = merge(local.postgresql_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + name = string + resource_group = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.postgresql_flexible_servers_with_connection_throttling_disabled_default_action + enum = local.postgresql_flexible_servers_with_connection_throttling_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.postgresql_flexible_servers_with_connection_throttling_disabled_enabled_actions + enum = local.postgresql_flexible_servers_with_connection_throttling_disabled_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} PostgreSQL flexible server(s) with connection throttling disabled." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.id => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_postgresql_flexible_server_with_connection_throttling_disabled + args = { + title = each.value.title + name = each.value.name + resource_group = each.value.resource_group + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_postgresql_flexible_server_with_connection_throttling_disabled" { + title = "Correct PostgreSQL flexible server with connection throttling disabled" + description = "Enable connection throttling for a PostgreSQL flexible server with connection throttling disabled." + tags = merge(local.postgresql_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "name" { + type = string + description = "The name of the PostgreSQL flexible server." + } + + param "resource_group" { + type = string + description = local.description_resource_group + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.postgresql_flexible_servers_with_connection_throttling_disabled_default_action + enum = local.postgresql_flexible_servers_with_connection_throttling_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.postgresql_flexible_servers_with_connection_throttling_disabled_enabled_actions + enum = local.postgresql_flexible_servers_with_connection_throttling_disabled_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected PostgreSQL flexible server ${param.title} with connection throttling disabled." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped PostgreSQL flexible server ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "enable_connection_throttling" = { + label = "Enable connection throttling" + value = "enable_connection_throttling" + style = local.style_alert + pipeline_ref = azure.pipeline.set_postgres_flexible_server_configuration + pipeline_args = { + server_name = param.name + resource_group = param.resource_group + subscription_id = param.subscription_id + conn = param.conn + config_name = "connection_throttle.enable" + config_value = "on" + } + success_msg = "Enabled connection throttling for PostgreSQL flexible server ${param.title}." + error_msg = "Error enabling connection throttling for PostgreSQL flexible server ${param.title}." + } + } + } + } +} + + diff --git a/pipelines/postgresql/postgresql_flexible_servers_with_log_checkpoints_disabled.fp b/pipelines/postgresql/postgresql_flexible_servers_with_log_checkpoints_disabled.fp new file mode 100644 index 0000000..dbf4211 --- /dev/null +++ b/pipelines/postgresql/postgresql_flexible_servers_with_log_checkpoints_disabled.fp @@ -0,0 +1,322 @@ +locals { + postgresql_flexible_servers_with_log_checkpoints_disabled_query = <<-EOQ + select + concat(id, ' [', subscription_id, '/', resource_group, ']') as title, + id as id, + name, + resource_group, + subscription_id, + _ctx ->> 'connection_name' as conn + from + azure_postgresql_flexible_server as db, + jsonb_array_elements(flexible_server_configurations) config + where + config ->> 'Name' = 'log_checkpoints' + and lower(config -> 'ConfigurationProperties' ->> 'value') != 'on'; + EOQ + + postgresql_flexible_servers_with_log_checkpoints_disabled_enabled_actions_enum = ["skip", "enable_log_checkpoints"] + postgresql_flexible_servers_with_log_checkpoints_disabled_default_action_enum = ["notify", "skip", "enable_log_checkpoints"] +} + +variable "postgresql_flexible_servers_with_log_checkpoints_disabled_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +variable "postgresql_flexible_servers_with_log_checkpoints_disabled_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +variable "postgresql_flexible_servers_with_log_checkpoints_disabled_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +variable "postgresql_flexible_servers_with_log_checkpoints_disabled_enabled_actions" { + type = list(string) + description = "The list of enabled actions to provide to approvers for selection." + default = ["skip", "enable_log_checkpoints"] + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +trigger "query" "detect_and_correct_postgresql_flexible_servers_with_log_checkpoints_disabled" { + title = "Detect & correct PostgreSQL flexible servers with log checkpoints disabled" + description = "Detect PostgreSQL flexible servers with log checkpoints disabled and then enable log checkpoints." + tags = local.postgresql_common_tags + + enabled = var.postgresql_flexible_servers_with_log_checkpoints_disabled_trigger_enabled + schedule = var.postgresql_flexible_servers_with_log_checkpoints_disabled_trigger_schedule + database = var.database + sql = local.postgresql_flexible_servers_with_log_checkpoints_disabled_query + + capture "insert" { + pipeline = pipeline.correct_postgresql_flexible_servers_with_log_checkpoints_disabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_postgresql_flexible_servers_with_log_checkpoints_disabled" { + title = "Detect & correct PostgreSQL flexible servers with log checkpoints disabled" + description = "Detect PostgreSQL flexible servers with log checkpoints disabled and then enable log checkpoints." + tags = merge(local.postgresql_common_tags, { recommended = "true" }) + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.postgresql_flexible_servers_with_log_checkpoints_disabled_default_action + enum = local.postgresql_flexible_servers_with_log_checkpoints_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.postgresql_flexible_servers_with_log_checkpoints_disabled_enabled_actions + enum = local.postgresql_flexible_servers_with_log_checkpoints_disabled_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.postgresql_flexible_servers_with_log_checkpoints_disabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_postgresql_flexible_servers_with_log_checkpoints_disabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_postgresql_flexible_servers_with_log_checkpoints_disabled" { + title = "Correct PostgreSQL flexible servers with log checkpoints disabled" + description = "Enable log checkpoints for PostgreSQL flexible servers with log checkpoints disabled." + tags = merge(local.postgresql_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + name = string + resource_group = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.postgresql_flexible_servers_with_log_checkpoints_disabled_default_action + enum = local.postgresql_flexible_servers_with_log_checkpoints_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.postgresql_flexible_servers_with_log_checkpoints_disabled_enabled_actions + enum = local.postgresql_flexible_servers_with_log_checkpoints_disabled_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} PostgreSQL flexible server(s) with log checkpoints disabled." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.id => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_postgresql_flexible_server_with_log_checkpoints_disabled + args = { + title = each.value.title + name = each.value.name + resource_group = each.value.resource_group + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_postgresql_flexible_server_with_log_checkpoints_disabled" { + title = "Correct PostgreSQL flexible server with log checkpoints disabled" + description = "Enable log checkpoints for a PostgreSQL flexible server with log checkpoints disabled." + tags = merge(local.postgresql_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "name" { + type = string + description = "The name of the PostgreSQL flexible server." + } + + param "resource_group" { + type = string + description = local.description_resource_group + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.postgresql_flexible_servers_with_log_checkpoints_disabled_default_action + enum = local.postgresql_flexible_servers_with_log_checkpoints_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.postgresql_flexible_servers_with_log_checkpoints_disabled_enabled_actions + enum = local.postgresql_flexible_servers_with_log_checkpoints_disabled_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected PostgreSQL flexible server ${param.title} with log checkpoints disabled." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped PostgreSQL flexible server ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "enable_log_checkpoints" = { + label = "Enable log checkpoints" + value = "enable_log_checkpoints" + style = local.style_alert + pipeline_ref = azure.pipeline.set_postgres_flexible_server_configuration + pipeline_args = { + server_name = param.name + resource_group = param.resource_group + subscription_id = param.subscription_id + conn = param.conn + config_name = "log_checkpoints" + config_value = "on" + } + success_msg = "Enabled log checkpoints for PostgreSQL flexible server ${param.title}." + error_msg = "Error enabling log checkpoints for PostgreSQL flexible server ${param.title}." + } + } + } + } +} diff --git a/pipelines/postgresql/postgresql_flexible_servers_with_ssl_disabled.fp b/pipelines/postgresql/postgresql_flexible_servers_with_ssl_disabled.fp new file mode 100644 index 0000000..c879670 --- /dev/null +++ b/pipelines/postgresql/postgresql_flexible_servers_with_ssl_disabled.fp @@ -0,0 +1,332 @@ +locals { + postgresql_flexible_servers_with_ssl_disabled_query = <<-EOQ + with ssl_enabled as( + select + id + from + azure_postgresql_flexible_server, + jsonb_array_elements(flexible_server_configurations) as config + where + config ->> 'Name' = 'require_secure_transport' and config -> 'ConfigurationProperties' ->> 'value' = 'Off' + ) + select + s.id as id, + s.name as name, + s.resource_group as resource_group, + s.subscription_id as subscription_id, + s._ctx ->> 'connection_name' as conn + from + azure_postgresql_flexible_server as s + left join ssl_enabled as a on s.id = a.id, + azure_subscription as sub + where + sub.subscription_id = s.subscription_id; + EOQ + + postgresql_flexible_servers_with_ssl_disabled_enabled_actions_enum = ["skip", "set_parameter_require_secure_transport"] + postgresql_flexible_servers_with_ssl_disabled_default_action_enum = ["notify", "skip", "set_parameter_require_secure_transport"] +} + +variable "postgresql_flexible_servers_with_ssl_disabled_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +variable "postgresql_flexible_servers_with_ssl_disabled_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +variable "postgresql_flexible_servers_with_ssl_disabled_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + enum = ["notify", "skip", "set_parameter_require_secure_transport"] + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +variable "postgresql_flexible_servers_with_ssl_disabled_enabled_actions" { + type = list(string) + description = "The list of enabled actions to provide to approvers for selection." + default = ["skip", "set_parameter_require_secure_transport"] + enum = ["skip", "set_parameter_require_secure_transport"] + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +trigger "query" "detect_and_correct_postgresql_flexible_servers_with_ssl_disabled" { + title = "Detect & correct PostgreSQL flexible servers with SSL disabled" + description = "Detect PostgreSQL flexible servers with SSL disabled and then enable SSL." + tags = local.postgresql_common_tags + + enabled = var.postgresql_flexible_servers_with_ssl_disabled_trigger_enabled + schedule = var.postgresql_flexible_servers_with_ssl_disabled_trigger_schedule + database = var.database + sql = local.postgresql_flexible_servers_with_ssl_disabled_query + + capture "insert" { + pipeline = pipeline.correct_postgresql_flexible_servers_with_ssl_disabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_postgresql_flexible_servers_with_ssl_disabled" { + title = "Detect & correct PostgreSQL flexible servers with SSL disabled" + description = "Detect PostgreSQL flexible servers with SSL disabled and then enable SSL." + tags = merge(local.postgresql_common_tags, { recommended = "true" }) + + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.postgresql_flexible_servers_with_ssl_disabled_default_action + enum = local.postgresql_flexible_servers_with_ssl_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.postgresql_flexible_servers_with_ssl_disabled_enabled_actions + enum = local.postgresql_flexible_servers_with_ssl_disabled_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.postgresql_flexible_servers_with_ssl_disabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_postgresql_flexible_servers_with_ssl_disabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_postgresql_flexible_servers_with_ssl_disabled" { + title = "Correct PostgreSQL flexible servers with SSL disabled" + description = "Enable SSL for PostgreSQL flexible servers with SSL disabled." + tags = merge(local.postgresql_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + name = string + resource_group = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.postgresql_flexible_servers_with_ssl_disabled_default_action + enum = local.postgresql_flexible_servers_with_ssl_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.postgresql_flexible_servers_with_ssl_disabled_enabled_actions + enum = local.postgresql_flexible_servers_with_ssl_disabled_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} PostgreSQL flexible server(s) with SSL disabled." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.id => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_postgresql_flexible_server_with_ssl_disabled + args = { + title = each.value.title + name = each.value.name + resource_group = each.value.resource_group + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_postgresql_flexible_server_with_ssl_disabled" { + title = "Correct PostgreSQL flexible server with SSL disabled" + description = "Enable SSL for a PostgreSQL flexible server with SSL disabled" + tags = merge(local.postgresql_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "name" { + type = string + description = "The name of the PostgreSQL flexible server." + } + + param "resource_group" { + type = string + description = local.description_resource_group + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.postgresql_flexible_servers_with_ssl_disabled_default_action + enum = local.postgresql_flexible_servers_with_ssl_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.postgresql_flexible_servers_with_ssl_disabled_enabled_actions + enum = local.postgresql_flexible_servers_with_ssl_disabled_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected PostgreSQL flexible server ${param.title} with SSL disabled." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped PostgreSQL flexible server ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "set_parameter_require_secure_transport" = { + label = "Set require secure transport parameter to 'On'" + value = "set_parameter_require_secure_transport" + style = local.style_alert + pipeline_ref = azure.pipeline.set_postgres_flexible_server_require_secure_transport + pipeline_args = { + server_name = param.name + resource_group = param.resource_group + subscription_id = param.subscription_id + conn = param.conn + require_secure_transport = "On" + } + success_msg = "Enabled SSL for PostgreSQL flexible server ${param.title}." + error_msg = "Error enabling SSL for PostgreSQL flexible server ${param.title}." + } + } + } + } +} diff --git a/pipelines/postgresql/postgresql_servers_log_retention_days_less_than_or_equal_to_3_days.fp b/pipelines/postgresql/postgresql_servers_log_retention_days_less_than_or_equal_to_3_days.fp new file mode 100644 index 0000000..3278215 --- /dev/null +++ b/pipelines/postgresql/postgresql_servers_log_retention_days_less_than_or_equal_to_3_days.fp @@ -0,0 +1,354 @@ +locals { + postgresql_servers_with_log_retention_less_than_or_equal_to_3_days_query = <<-EOQ + select + concat(db.id, ' [', db.subscription_id, '/', db.resource_group, ']') as title, + db.id as id, + db.name, + db.resource_group, + db.subscription_id, + db._ctx ->> 'connection_name' as conn + from + azure_postgresql_server as db, + jsonb_array_elements(server_configurations) config, + azure_subscription as sub + where + config ->> 'Name' = 'log_retention_days' + and (config -> 'ConfigurationProperties' ->> 'value') :: integer <= 3 + and sub.subscription_id = db.subscription_id; + EOQ + + postgresql_servers_with_log_retention_less_than_or_equal_to_3_days_enabled_actions_enum = ["skip", "update_log_retention_days"] + postgresql_servers_with_log_retention_less_than_or_equal_to_3_days_default_action_enum = ["notify", "skip", "update_log_retention_days"] +} + +variable "postgresql_servers_with_log_retention_less_than_or_equal_to_3_days_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +variable "postgresql_servers_with_log_retention_less_than_or_equal_to_3_days_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +variable "postgresql_servers_with_log_retention_less_than_or_equal_to_3_days_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +variable "postgresql_servers_with_log_retention_less_than_or_equal_to_3_days_enabled_actions" { + type = list(string) + description = "The list of enabled actions to provide to approvers for selection." + default = ["skip", "update_log_retention_days"] + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +variable "postgresql_servers_with_log_retention_less_than_or_equal_to_3_days_log_retention_days" { + type = string + description = "The number of days logs should be retained." + default = "7" + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +trigger "query" "detect_and_correct_postgresql_servers_with_log_retention_less_than_or_equal_to_3_days" { + title = "Detect & correct PostgreSQL servers with log retention less than 3 days" + description = "Detect PostgreSQL servers with log retention less than 3 and then sets log retention to 3 or more than 3 days." + tags = local.postgresql_common_tags + + enabled = var.postgresql_servers_with_log_retention_less_than_or_equal_to_3_days_trigger_enabled + schedule = var.postgresql_servers_with_log_retention_less_than_or_equal_to_3_days_trigger_schedule + database = var.database + sql = local.postgresql_servers_with_log_retention_less_than_or_equal_to_3_days_query + + capture "insert" { + pipeline = pipeline.correct_postgresql_servers_with_log_retention_less_than_or_equal_to_3_days + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_postgresql_servers_with_log_retention_less_than_or_equal_to_3_days" { + title = "Detect & correct PostgreSQL servers with log retention less than 3 days" + description = "Detect PostgreSQL servers with log retention less than 3 and then sets log retention to 3 or more than 3 days." + tags = merge(local.postgresql_common_tags, { recommended = "true" }) + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.postgresql_servers_with_log_retention_less_than_or_equal_to_3_days_default_action + enum = local.postgresql_servers_with_log_retention_less_than_or_equal_to_3_days_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.postgresql_servers_with_log_retention_less_than_or_equal_to_3_days_enabled_actions + enum = local.postgresql_servers_with_log_retention_less_than_or_equal_to_3_days_enabled_actions_enum + } + + param "log_retention_days" { + type = string + description = "The number of days logs should be retained." + default = var.postgresql_servers_with_log_retention_less_than_or_equal_to_3_days_log_retention_days + } + + step "query" "detect" { + database = param.database + sql = local.postgresql_servers_with_log_retention_less_than_or_equal_to_3_days_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_postgresql_servers_with_log_retention_less_than_or_equal_to_3_days + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + log_retention_days = param.log_retention_days + } + } +} + +pipeline "correct_postgresql_servers_with_log_retention_less_than_or_equal_to_3_days" { + title = "Correct PostgreSQL servers with log retention less than 3 days" + description = "Update log retention days to 3 or more for PostgreSQL servers with log retention less than 3 days." + tags = merge(local.postgresql_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + name = string + resource_group = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.postgresql_servers_with_log_retention_less_than_or_equal_to_3_days_default_action + enum = local.postgresql_servers_with_log_retention_less_than_or_equal_to_3_days_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.postgresql_servers_with_log_retention_less_than_or_equal_to_3_days_enabled_actions + enum = local.postgresql_servers_with_log_retention_less_than_or_equal_to_3_days_enabled_actions_enum + } + + param "log_retention_days" { + type = string + description = "The number of days logs should be retained." + default = var.postgresql_servers_with_log_retention_less_than_or_equal_to_3_days_log_retention_days + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} PostgreSQL server(s) with log retention_days less than 3 days." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.id => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_postgresql_server_with_log_retention_less_than_3_days + args = { + title = each.value.title + name = each.value.name + resource_group = each.value.resource_group + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + log_retention_days = param.log_retention_days + } + } +} + +pipeline "correct_one_postgresql_server_with_log_retention_less_than_3_days" { + title = "Correct PostgreSQL server with log retention less than 3 days" + description = "Update log retention days to 3 or more for a PostgreSQL server with log retention less than 3 days." + tags = merge(local.postgresql_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "name" { + type = string + description = "The name of the PostgreSQL server." + } + + param "resource_group" { + type = string + description = local.description_resource_group + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.postgresql_servers_with_log_retention_less_than_or_equal_to_3_days_default_action + enum = local.postgresql_servers_with_log_retention_less_than_or_equal_to_3_days_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.postgresql_servers_with_log_retention_less_than_or_equal_to_3_days_enabled_actions + enum = local.postgresql_servers_with_log_retention_less_than_or_equal_to_3_days_enabled_actions_enum + } + + param "log_retention_days" { + type = string + description = "The number of days logs should be retained." + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected PostgreSQL server ${param.title} with log retention less than 3 days." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped PostgreSQL DB server ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "update_log_retention_days" = { + label = "Update log retention days" + value = "update_log_retention_days" + style = local.style_alert + pipeline_ref = azure.pipeline.set_postgres_server_configuration + pipeline_args = { + server_name = param.name + resource_group = param.resource_group + subscription_id = param.subscription_id + conn = param.conn + config_name = "log_retention_days" + config_value = param.log_retention_days + } + success_msg = "Updated log retention days for PostgreSQL server ${param.title}." + error_msg = "Error updating log retention days for PostgreSQL server ${param.title}." + } + } + } + } +} + diff --git a/pipelines/postgresql/postgresql_servers_with_allow_access_to_azure_services_enabled.fp b/pipelines/postgresql/postgresql_servers_with_allow_access_to_azure_services_enabled.fp new file mode 100644 index 0000000..2a2a265 --- /dev/null +++ b/pipelines/postgresql/postgresql_servers_with_allow_access_to_azure_services_enabled.fp @@ -0,0 +1,332 @@ +locals { + postgresql_servers_with_allow_access_to_azure_services_enabled_query = <<-EOQ + with postgres_db_with_allow_access_to_azure_services as ( + select + id + from + azure_postgresql_server, + jsonb_array_elements(firewall_rules) as r + where + r -> 'FirewallRuleProperties' ->> 'endIpAddress' = '0.0.0.0' + and r -> 'FirewallRuleProperties' ->> 'startIpAddress' = '0.0.0.0' + ) + select + concat(db.id, ' [', db.subscription_id, '/', db.resource_group, ']') as title, + db.id as id, + db.name, + db.resource_group, + db.subscription_id, + db._ctx ->> 'connection_name' as conn + from + azure_postgresql_server as db, + postgres_db_with_allow_access_to_azure_services as a, + azure_subscription as sub + where + a.id is not null + and sub.subscription_id = db.subscription_id; + EOQ + + postgresql_servers_with_allow_access_to_azure_services_enabled_enabled_actions_enum = ["skip", "delete_allow_all_windows_azure_ips_firewall_rule"] + postgresql_servers_with_allow_access_to_azure_services_enabled_default_action_enum = ["notify", "skip", "delete_allow_all_windows_azure_ips_firewall_rule"] +} + +variable "postgresql_servers_with_allow_access_to_azure_services_enabled_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +variable "postgresql_servers_with_allow_access_to_azure_services_enabled_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +variable "postgresql_servers_with_allow_access_to_azure_services_enabled_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +variable "postgresql_servers_with_allow_access_to_azure_services_enabled_enabled_actions" { + type = list(string) + description = "The list of enabled actions to provide to approvers for selection." + default = ["skip", "delete_allow_all_windows_azure_ips_firewall_rule"] + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +trigger "query" "detect_and_correct_postgresql_servers_with_allow_access_to_azure_services_enabled" { + title = "Detect & correct PostgreSQL servers allowing access to Azure services" + description = "Detect PostgreSQL servers allowing access to Azure services and then disable access to Azure services." + tags = local.postgresql_common_tags + + enabled = var.postgresql_servers_with_allow_access_to_azure_services_enabled_trigger_enabled + schedule = var.postgresql_servers_with_allow_access_to_azure_services_enabled_trigger_schedule + database = var.database + sql = local.postgresql_servers_with_allow_access_to_azure_services_enabled_query + + capture "insert" { + pipeline = pipeline.correct_postgresql_servers_with_allow_access_to_azure_services_enabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_postgresql_servers_with_allow_access_to_azure_services_enabled" { + title = "Detect & correct PostgreSQL servers allowing access to Azure services" + description = "Detect PostgreSQL servers allowing access to Azure services and then disable access to Azure services." + tags = merge(local.postgresql_common_tags, { recommended = "true" }) + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.postgresql_servers_with_allow_access_to_azure_services_enabled_default_action + enum = local.postgresql_servers_with_allow_access_to_azure_services_enabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.postgresql_servers_with_allow_access_to_azure_services_enabled_enabled_actions + enum = local.postgresql_servers_with_allow_access_to_azure_services_enabled_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.postgresql_servers_with_allow_access_to_azure_services_enabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_postgresql_servers_with_allow_access_to_azure_services_enabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_postgresql_servers_with_allow_access_to_azure_services_enabled" { + title = "Correct PostgreSQL servers allowing access to Azure services" + description = "Disable access to Azure services for PostgreSQL servers with enabled access to Azure services." + tags = merge(local.postgresql_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + name = string + resource_group = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.postgresql_servers_with_allow_access_to_azure_services_enabled_default_action + enum = local.postgresql_servers_with_allow_access_to_azure_services_enabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.postgresql_servers_with_allow_access_to_azure_services_enabled_enabled_actions + enum = local.postgresql_servers_with_allow_access_to_azure_services_enabled_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} PostgreSQL server(s) allowing access to Azure services." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.id => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_postgresql_server_with_allow_access_to_azure_services_enabled + args = { + title = each.value.title + name = each.value.name + resource_group = each.value.resource_group + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_postgresql_server_with_allow_access_to_azure_services_enabled" { + title = "Correct PostgreSQL server allowing access to Azure services" + description = "Disable access to Azure services for a PostgreSQL server with enabled access to Azure services." + tags = merge(local.postgresql_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "name" { + type = string + description = "The name of the PostgreSQL server." + } + + param "resource_group" { + type = string + description = local.description_resource_group + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.postgresql_servers_with_allow_access_to_azure_services_enabled_default_action + enum = local.postgresql_servers_with_allow_access_to_azure_services_enabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.postgresql_servers_with_allow_access_to_azure_services_enabled_enabled_actions + enum = local.postgresql_servers_with_allow_access_to_azure_services_enabled_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected PostgreSQL server ${param.title} allowing access to Azure services." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped PostgreSQL server ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "delete_allow_all_windows_azure_ips_firewall_rule" = { + label = "Delete Firewall Rule" + value = "delete_allow_all_windows_azure_ips_firewall_rule" + style = local.style_alert + pipeline_ref = azure.pipeline.delete_postgres_server_firewall_rule + pipeline_args = { + resource_group = param.resource_group + subscription_id = param.subscription_id + server_name = param.name + conn = param.conn + firewall_rule_name = "AllowAllWindowsAzureIps" + } + success_msg = "Deleted firewall rule allowing access to Azure services for PostgreSQL server ${param.title}." + error_msg = "Error deleting firewall rule allowing access to Azure services for PostgreSQL server ${param.title}." + } + } + } + } +} diff --git a/pipelines/postgresql/postgresql_servers_with_connection_throttling_disabled.fp b/pipelines/postgresql/postgresql_servers_with_connection_throttling_disabled.fp new file mode 100644 index 0000000..470ea06 --- /dev/null +++ b/pipelines/postgresql/postgresql_servers_with_connection_throttling_disabled.fp @@ -0,0 +1,326 @@ +locals { + postgresql_servers_with_connection_throttling_disabled_query = <<-EOQ + select + concat(db.id, ' [', db.subscription_id, '/', db.resource_group, ']') as title, + db.id as id, + db.name, + db.resource_group, + db.subscription_id, + db._ctx ->> 'connection_name' as conn + from + azure_postgresql_server as db, + jsonb_array_elements(server_configurations) config, + azure_subscription as sub + where + config ->> 'Name' = 'connection_throttling' + and lower(config -> 'ConfigurationProperties' ->> 'value') != 'on' + and sub.subscription_id = db.subscription_id; + EOQ + + postgresql_servers_with_connection_throttling_disabled_enabled_actions_enum = ["skip", "enable_connection_throttling"] + postgresql_servers_with_connection_throttling_disabled_default_action_enum = ["notify", "skip", "enable_connection_throttling"] +} + +variable "postgresql_servers_with_connection_throttling_disabled_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +variable "postgresql_servers_with_connection_throttling_disabled_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +variable "postgresql_servers_with_connection_throttling_disabled_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +variable "postgresql_servers_with_connection_throttling_disabled_enabled_actions" { + type = list(string) + description = "The list of enabled actions to provide to approvers for selection." + default = ["skip", "enable_connection_throttling"] + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +trigger "query" "detect_and_correct_postgresql_servers_with_connection_throttling_disabled" { + title = "Detect & correct PostgreSQL servers with connection throttling disabled" + description = "Detect PostgreSQL servers with connection throttling disabled and then enable connection throttling." + tags = local.postgresql_common_tags + + enabled = var.postgresql_servers_with_connection_throttling_disabled_trigger_enabled + schedule = var.postgresql_servers_with_connection_throttling_disabled_trigger_schedule + database = var.database + sql = local.postgresql_servers_with_connection_throttling_disabled_query + + capture "insert" { + pipeline = pipeline.correct_postgresql_servers_with_connection_throttling_disabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_postgresql_servers_with_connection_throttling_disabled" { + title = "Detect & correct PostgreSQL servers with connection throttling disabled" + description = "Detect PostgreSQL servers with connection throttling disabled and then enable connection throttling" + tags = local.postgresql_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.postgresql_servers_with_connection_throttling_disabled_default_action + enum = local.postgresql_servers_with_connection_throttling_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.postgresql_servers_with_connection_throttling_disabled_enabled_actions + enum = local.postgresql_servers_with_connection_throttling_disabled_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.postgresql_servers_with_connection_throttling_disabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_postgresql_servers_with_connection_throttling_disabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_postgresql_servers_with_connection_throttling_disabled" { + title = "Correct PostgreSQL servers with connection throttling disabled" + description = "Enable connection throttling for PostgreSQL servers with connection throttling disabled." + tags = merge(local.postgresql_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + name = string + resource_group = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.postgresql_servers_with_connection_throttling_disabled_default_action + enum = local.postgresql_servers_with_connection_throttling_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.postgresql_servers_with_connection_throttling_disabled_enabled_actions + enum = local.postgresql_servers_with_connection_throttling_disabled_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} PostgreSQL server(s) with connection throttling disabled." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.id => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_postgresql_server_with_connection_throttling_disabled + args = { + title = each.value.title + name = each.value.name + resource_group = each.value.resource_group + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_postgresql_server_with_connection_throttling_disabled" { + title = "Correct PostgreSQL server with connection throttling disabled" + description = "Enable connection throttling for a PostgreSQL server with connection throttling disabled." + tags = merge(local.postgresql_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "name" { + type = string + description = "The name of the PostgreSQL server." + } + + param "resource_group" { + type = string + description = local.description_resource_group + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.postgresql_servers_with_connection_throttling_disabled_default_action + enum = local.postgresql_servers_with_connection_throttling_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.postgresql_servers_with_connection_throttling_disabled_enabled_actions + enum = local.postgresql_servers_with_connection_throttling_disabled_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected PostgreSQL server ${param.title} with connection throttling disabled." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped PostgreSQL server ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "enable_connection_throttling" = { + label = "On connection throttling" + value = "enable_connection_throttling" + style = local.style_alert + pipeline_ref = azure.pipeline.set_postgres_server_configuration + pipeline_args = { + server_name = param.name + resource_group = param.resource_group + subscription_id = param.subscription_id + conn = param.conn + config_name = "connection_throttling" + config_value = "on" + } + success_msg = "Enable connection throttling for PostgreSQL server ${param.title}." + error_msg = "Error enabling connection throttling for PostgreSQL server ${param.title}." + } + } + } + } +} + + diff --git a/pipelines/postgresql/postgresql_servers_with_infrastructure_encryption_disabled.fp b/pipelines/postgresql/postgresql_servers_with_infrastructure_encryption_disabled.fp new file mode 100644 index 0000000..648ed6d --- /dev/null +++ b/pipelines/postgresql/postgresql_servers_with_infrastructure_encryption_disabled.fp @@ -0,0 +1,132 @@ +locals { + postgresql_servers_with_infrastructure_encryption_disabled_query = <<-EOQ + select + concat(id, ' [', subscription_id, '/', resource_group, ']') as title, + id as id, + name, + resource_group, + subscription_id, + _ctx ->> 'connection_name' as conn + from + azure_postgresql_server + where + not (public_network_access = 'Enabled'); + EOQ +} + +variable "postgresql_servers_with_infrastructure_encryption_disabled_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +variable "postgresql_servers_with_infrastructure_encryption_disabled_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +trigger "query" "detect_and_correct_postgresql_servers_with_infrastructure_encryption_disabled" { + title = "Detect & correct PostgreSQL servers with infrastructure encryption disabled" + description = "Detect PostgreSQL servers with infrastructure encryption disabled." + tags = local.postgresql_common_tags + + enabled = var.postgresql_servers_with_infrastructure_encryption_disabled_trigger_enabled + schedule = var.postgresql_servers_with_infrastructure_encryption_disabled_trigger_schedule + database = var.database + sql = local.postgresql_servers_with_infrastructure_encryption_disabled_query + + capture "insert" { + pipeline = pipeline.correct_postgresql_servers_with_infrastructure_encryption_disabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_postgresql_servers_with_infrastructure_encryption_disabled" { + title = "Detect & correct PostgreSQL servers with infrastructure encryption disabled" + description = "Detect PostgreSQL servers with infrastructure encryption disabled." + tags = merge(local.postgresql_common_tags, { recommended = "true" }) + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.postgresql_servers_with_infrastructure_encryption_disabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_postgresql_servers_with_infrastructure_encryption_disabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_postgresql_servers_with_infrastructure_encryption_disabled" { + title = "Correct PostgreSQL servers with infrastructure encryption disabled" + description = "Send notifications for PostgreSQL servers with infrastructure encryption disabled." + tags = merge(local.postgresql_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} PostgreSQL server(s) with infrastructure encryption disabled." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected PostgreSQL server ${each.value.title} with infrastructure encryption disabled." + } +} \ No newline at end of file diff --git a/pipelines/postgresql/postgresql_servers_with_log_checkpoints_disabled.fp b/pipelines/postgresql/postgresql_servers_with_log_checkpoints_disabled.fp new file mode 100644 index 0000000..6c9a890 --- /dev/null +++ b/pipelines/postgresql/postgresql_servers_with_log_checkpoints_disabled.fp @@ -0,0 +1,324 @@ +locals { + postgresql_servers_with_log_checkpoints_disabled_query = <<-EOQ + select + concat(db.id, ' [', db.subscription_id, '/', db.resource_group, ']') as title, + db.id as id, + db.name, + db.resource_group, + db.subscription_id, + db._ctx ->> 'connection_name' as conn + from + azure_postgresql_server as db, + jsonb_array_elements(server_configurations) config, + azure_subscription as sub + where + config ->> 'Name' = 'log_checkpoints' + and lower(config -> 'ConfigurationProperties' ->> 'value') != 'on' + and sub.subscription_id = db.subscription_id; + EOQ + + postgresql_servers_with_log_checkpoints_disabled_enabled_actions_enum = ["skip", "enable_log_checkpoints"] + postgresql_servers_with_log_checkpoints_disabled_default_action_enum = ["notify", "skip", "enable_log_checkpoints"] +} + +variable "postgresql_servers_with_log_checkpoints_disabled_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +variable "postgresql_servers_with_log_checkpoints_disabled_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +variable "postgresql_servers_with_log_checkpoints_disabled_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +variable "postgresql_servers_with_log_checkpoints_disabled_enabled_actions" { + type = list(string) + description = "The list of enabled actions to provide to approvers for selection." + default = ["skip", "enable_log_checkpoints"] + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +trigger "query" "detect_and_correct_postgresql_servers_with_log_checkpoints_disabled" { + title = "Detect & correct PostgreSQL servers with log checkpoints disabled" + description = "Detect PostgreSQL servers with log checkpoints disabled and then enable log checkpoints." + tags = local.postgresql_common_tags + + enabled = var.postgresql_servers_with_log_checkpoints_disabled_trigger_enabled + schedule = var.postgresql_servers_with_log_checkpoints_disabled_trigger_schedule + database = var.database + sql = local.postgresql_servers_with_log_checkpoints_disabled_query + + capture "insert" { + pipeline = pipeline.correct_postgresql_servers_with_log_checkpoints_disabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_postgresql_servers_with_log_checkpoints_disabled" { + title = "Detect & correct PostgreSQL servers with log checkpoints disabled" + description = "Detect PostgreSQL servers with log checkpoints disabled and then enable log checkpoints." + tags = merge(local.postgresql_common_tags, { recommended = "true" }) + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.postgresql_servers_with_log_checkpoints_disabled_default_action + enum = local.postgresql_servers_with_log_checkpoints_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.postgresql_servers_with_log_checkpoints_disabled_enabled_actions + enum = local.postgresql_servers_with_log_checkpoints_disabled_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.postgresql_servers_with_log_checkpoints_disabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_postgresql_servers_with_log_checkpoints_disabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_postgresql_servers_with_log_checkpoints_disabled" { + title = "Correct PostgreSQL servers with log checkpoints disabled" + description = "Enable log checkpoints for PostgreSQL servers with log checkpoints disabled." + tags = merge(local.postgresql_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + name = string + resource_group = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.postgresql_servers_with_log_checkpoints_disabled_default_action + enum = local.postgresql_servers_with_log_checkpoints_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.postgresql_servers_with_log_checkpoints_disabled_enabled_actions + enum = local.postgresql_servers_with_log_checkpoints_disabled_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} PostgreSQL server(s) with log checkpoints disabled." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.id => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_postgresql_server_with_log_checkpoints_disabled + args = { + title = each.value.title + name = each.value.name + resource_group = each.value.resource_group + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_postgresql_server_with_log_checkpoints_disabled" { + title = "Correct PostgreSQL server with log checkpoints disabled" + description = "Enable log checkpoints for a PostgreSQL server with log checkpoints disabled." + tags = merge(local.postgresql_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "name" { + type = string + description = "The name of the PostgreSQL server." + } + + param "resource_group" { + type = string + description = local.description_resource_group + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.postgresql_servers_with_log_checkpoints_disabled_default_action + enum = local.postgresql_servers_with_log_checkpoints_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.postgresql_servers_with_log_checkpoints_disabled_enabled_actions + enum = local.postgresql_servers_with_log_checkpoints_disabled_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected PostgreSQL server ${param.title} with log checkpoints disabled." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped PostgreSQL DB server ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "enable_log_checkpoints" = { + label = "Enable log checkpoints" + value = "enable_log_checkpoints" + style = local.style_alert + pipeline_ref = azure.pipeline.set_postgres_server_configuration + pipeline_args = { + server_name = param.name + resource_group = param.resource_group + subscription_id = param.subscription_id + conn = param.conn + config_name = "log_checkpoints" + config_value = "on" + } + success_msg = "Enabled log checkpoints for PostgreSQL server ${param.title}." + error_msg = "Error enabling log checkpoints for PostgreSQL server ${param.title}." + } + } + } + } +} diff --git a/pipelines/postgresql/postgresql_servers_with_log_connections_disabled.fp b/pipelines/postgresql/postgresql_servers_with_log_connections_disabled.fp new file mode 100644 index 0000000..f3da6df --- /dev/null +++ b/pipelines/postgresql/postgresql_servers_with_log_connections_disabled.fp @@ -0,0 +1,325 @@ +locals { + postgresql_servers_with_log_connections_disabled_query = <<-EOQ + select + concat(db.id, ' [', db.subscription_id, '/', db.resource_group, ']') as title, + db.id as id, + db.name, + db.resource_group, + db.subscription_id, + db._ctx ->> 'connection_name' as conn + from + azure_postgresql_server as db, + jsonb_array_elements(server_configurations) config, + azure_subscription as sub + where + config ->> 'Name' = 'log_connections' + and lower(config -> 'ConfigurationProperties' ->> 'value') != 'on' + and sub.subscription_id = db.subscription_id; + EOQ + + postgresql_servers_with_log_connections_disabled_enabled_actions_enum = ["skip", "enable_logging_connections"] + postgresql_servers_with_log_connections_disabled_default_action_enum = ["notify", "skip", "enable_logging_connections"] +} + +variable "postgresql_servers_with_log_connections_disabled_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +variable "postgresql_servers_with_log_connections_disabled_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +variable "postgresql_servers_with_log_connections_disabled_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +variable "postgresql_servers_with_log_connections_disabled_enabled_actions" { + type = list(string) + description = "The list of enabled actions to provide to approvers for selection." + default = ["skip", "enable_logging_connections"] + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +trigger "query" "detect_and_correct_postgresql_servers_with_log_connections_disabled" { + title = "Detect & correct PostgreSQL servers with logging connections disabled" + description = "Detect PostgreSQL servers with logging connections disabled and then enable logging connections." + tags = local.postgresql_common_tags + + enabled = var.postgresql_servers_with_log_connections_disabled_trigger_enabled + schedule = var.postgresql_servers_with_log_connections_disabled_trigger_schedule + database = var.database + sql = local.postgresql_servers_with_log_connections_disabled_query + + capture "insert" { + pipeline = pipeline.correct_postgresql_servers_with_log_connections_disabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_postgresql_servers_with_log_connections_disabled" { + title = "Detect & correct PostgreSQL servers with logging connections disabled" + description = "Detect PostgreSQL servers with logging connections disabled and then enable logging connections." + tags = merge(local.postgresql_common_tags, { recommended = "true" }) + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.postgresql_servers_with_log_connections_disabled_default_action + enum = local.postgresql_servers_with_log_connections_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.postgresql_servers_with_log_connections_disabled_enabled_actions + enum = local.postgresql_servers_with_log_connections_disabled_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.postgresql_servers_with_log_connections_disabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_postgresql_servers_with_log_connections_disabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_postgresql_servers_with_log_connections_disabled" { + title = "Correct PostgreSQL servers with logging connections disabled" + description = "Enable logging connections for PostgreSQL servers with logging connections disabled." + tags = merge(local.postgresql_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + name = string + resource_group = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.postgresql_servers_with_log_connections_disabled_default_action + enum = local.postgresql_servers_with_log_connections_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.postgresql_servers_with_log_connections_disabled_enabled_actions + enum = local.postgresql_servers_with_log_connections_disabled_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} PostgreSQL server(s) with logging connections disabled." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.id => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_postgresql_server_with_log_connections_disabled + args = { + title = each.value.title + name = each.value.name + resource_group = each.value.resource_group + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_postgresql_server_with_log_connections_disabled" { + title = "Correct PostgreSQL server with logging connections disabled" + description = "Enable logging connections for a PostgreSQL server with logging connections disabled." + tags = merge(local.postgresql_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "name" { + type = string + description = "The name of the PostgreSQL server." + } + + param "resource_group" { + type = string + description = local.description_resource_group + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.postgresql_servers_with_log_connections_disabled_default_action + enum = local.postgresql_servers_with_log_connections_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.postgresql_servers_with_log_connections_disabled_enabled_actions + enum = local.postgresql_servers_with_log_connections_disabled_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected PostgreSQL server ${param.title} with logging connections disabled." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped PostgreSQL server ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "enable_logging_connections" = { + label = "Enable logging connections" + value = "enable_logging_connections" + style = local.style_alert + pipeline_ref = azure.pipeline.set_postgres_server_configuration + pipeline_args = { + server_name = param.name + resource_group = param.resource_group + subscription_id = param.subscription_id + conn = param.conn + config_name = "log_connections" + config_value = "on" + } + success_msg = "Enabled logging connections for PostgreSQL server ${param.title}." + error_msg = "Error enabling logging connections for PostgreSQL server ${param.title}." + } + } + } + } +} + diff --git a/pipelines/postgresql/postgresql_servers_with_log_disconnections_disabled.fp b/pipelines/postgresql/postgresql_servers_with_log_disconnections_disabled.fp new file mode 100644 index 0000000..e39bcd1 --- /dev/null +++ b/pipelines/postgresql/postgresql_servers_with_log_disconnections_disabled.fp @@ -0,0 +1,324 @@ +locals { + postgresql_servers_with_log_disconnections_disabled_query = <<-EOQ + select + concat(db.id, ' [', db.subscription_id, '/', db.resource_group, ']') as title, + db.id as id, + db.name, + db.resource_group, + db.subscription_id, + db._ctx ->> 'connection_name' as conn + from + azure_postgresql_server as db, + jsonb_array_elements(server_configurations) config, + azure_subscription as sub + where + config ->> 'Name' = 'log_disconnections' + and lower(config -> 'ConfigurationProperties' ->> 'value') != 'on' + and sub.subscription_id = db.subscription_id; + EOQ + + postgresql_servers_with_log_disconnections_disabled_enabled_actions_enum = ["skip", "enable_logging_disconnections"] + postgresql_servers_with_log_disconnections_disabled_default_action_enum = ["notify", "skip", "enable_logging_disconnections"] +} + +variable "postgresql_servers_with_log_disconnections_disabled_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +variable "postgresql_servers_with_log_disconnections_disabled_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +variable "postgresql_servers_with_log_disconnections_disabled_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +variable "postgresql_servers_with_log_disconnections_disabled_enabled_actions" { + type = list(string) + description = "The list of enabled actions to provide to approvers for selection." + default = ["skip", "enable_logging_disconnections"] + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +trigger "query" "detect_and_correct_postgresql_servers_with_log_disconnections_disabled" { + title = "Detect & correct PostgreSQL servers with logging disconnections disabled" + description = "Detect PostgreSQL servers with logging disconnections disabled and then enable logging disconnections." + tags = local.postgresql_common_tags + + enabled = var.postgresql_servers_with_log_disconnections_disabled_trigger_enabled + schedule = var.postgresql_servers_with_log_disconnections_disabled_trigger_schedule + database = var.database + sql = local.postgresql_servers_with_log_disconnections_disabled_query + + capture "insert" { + pipeline = pipeline.correct_postgresql_servers_with_log_disconnections_disabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_postgresql_servers_with_log_disconnections_disabled" { + title = "Detect & correct PostgreSQL servers with logging disconnections disabled" + description = "Detect PostgreSQL servers with logging disconnections disabled and then enable logging disconnections." + tags = merge(local.postgresql_common_tags, { recommended = "true" }) + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.postgresql_servers_with_log_disconnections_disabled_default_action + enum = local.postgresql_servers_with_log_disconnections_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.postgresql_servers_with_log_disconnections_disabled_enabled_actions + enum = local.postgresql_servers_with_log_disconnections_disabled_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.postgresql_servers_with_log_disconnections_disabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_postgresql_servers_with_log_disconnections_disabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_postgresql_servers_with_log_disconnections_disabled" { + title = "Correct PostgreSQL servers with logging disconnections disabled" + description = "Enable logging disconnections for PostgreSQL servers with logging disconnections disabled." + tags = merge(local.postgresql_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + name = string + resource_group = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.postgresql_servers_with_log_disconnections_disabled_default_action + enum = local.postgresql_servers_with_log_disconnections_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.postgresql_servers_with_log_disconnections_disabled_enabled_actions + enum = local.postgresql_servers_with_log_disconnections_disabled_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} PostgreSQL server(s) with logging disconnections disabled." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.id => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_postgresql_server_with_log_disconnections_disabled + args = { + title = each.value.title + name = each.value.name + resource_group = each.value.resource_group + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_postgresql_server_with_log_disconnections_disabled" { + title = "Correct PostgreSQL server with logging disconnections disabled" + description = "Enable logging disconnections for a PostgreSQL server with logging disconnections disabled." + tags = merge(local.postgresql_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "name" { + type = string + description = "The name of the PostgreSQL server." + } + + param "resource_group" { + type = string + description = local.description_resource_group + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.postgresql_servers_with_log_disconnections_disabled_default_action + enum = local.postgresql_servers_with_log_disconnections_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.postgresql_servers_with_log_disconnections_disabled_enabled_actions + enum = local.postgresql_servers_with_log_disconnections_disabled_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected PostgreSQL server ${param.title} with logging disconnections disabled." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped PostgreSQL server ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "enable_logging_disconnections" = { + label = "Enable logging disconnections" + value = "enable_logging_disconnections" + style = local.style_alert + pipeline_ref = azure.pipeline.set_postgres_server_configuration + pipeline_args = { + server_name = param.name + resource_group = param.resource_group + subscription_id = param.subscription_id + conn = param.conn + config_name = "log_disconnections" + config_value = "on" + } + success_msg = "Enabled logging disconnections for PostgreSQL server ${param.title}." + error_msg = "Error enabling logging disconnections for PostgreSQL server ${param.title}." + } + } + } + } +} diff --git a/pipelines/postgresql/postgresql_servers_with_log_duration_disabled.fp b/pipelines/postgresql/postgresql_servers_with_log_duration_disabled.fp new file mode 100644 index 0000000..4f42a3f --- /dev/null +++ b/pipelines/postgresql/postgresql_servers_with_log_duration_disabled.fp @@ -0,0 +1,324 @@ +locals { + postgresql_servers_with_log_duration_disabled_query = <<-EOQ + select + concat(db.id, ' [', db.subscription_id, '/', db.resource_group, ']') as title, + db.id as id, + db.name, + db.resource_group, + db.subscription_id, + db._ctx ->> 'connection_name' as conn + from + azure_postgresql_server as db, + jsonb_array_elements(server_configurations) config, + azure_subscription as sub + where + config ->> 'Name' = 'log_duration' + and lower(config -> 'ConfigurationProperties' ->> 'value') != 'on' + and sub.subscription_id = db.subscription_id; + EOQ + + postgresql_servers_with_log_duration_disabled_enabled_actions_enum = ["skip", "enable_logging_duration"] + postgresql_servers_with_log_duration_disabled_default_action_enum = ["notify", "skip", "enable_logging_duration"] +} + +variable "postgresql_servers_with_log_duration_disabled_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +variable "postgresql_servers_with_log_duration_disabled_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +variable "postgresql_servers_with_log_duration_disabled_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +variable "postgresql_servers_with_log_duration_disabled_enabled_actions" { + type = list(string) + description = "The list of enabled actions to provide to approvers for selection." + default = ["skip", "enable_logging_duration"] + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +trigger "query" "detect_and_correct_postgresql_servers_with_log_duration_disabled" { + title = "Detect & correct PostgreSQL servers with logging duration disabled" + description = "Detect PostgreSQL servers with logging duration disabled and then enable logging duration." + tags = local.postgresql_common_tags + + enabled = var.postgresql_servers_with_log_duration_disabled_trigger_enabled + schedule = var.postgresql_servers_with_log_duration_disabled_trigger_schedule + database = var.database + sql = local.postgresql_servers_with_log_duration_disabled_query + + capture "insert" { + pipeline = pipeline.correct_postgresql_servers_with_log_duration_disabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_postgresql_servers_with_log_duration_disabled" { + title = "Detect & correct PostgreSQL servers with logging duration disabled" + description = "Detect PostgreSQL servers with logging duration disabled and then enable logging duration." + tags = merge(local.postgresql_common_tags, { recommended = "true" }) + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.postgresql_servers_with_log_duration_disabled_default_action + enum = local.postgresql_servers_with_log_duration_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.postgresql_servers_with_log_duration_disabled_enabled_actions + enum = local.postgresql_servers_with_log_duration_disabled_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.postgresql_servers_with_log_duration_disabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_postgresql_servers_with_log_duration_disabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_postgresql_servers_with_log_duration_disabled" { + title = "Correct PostgreSQL servers with logging duration disabled" + description = "Enable logging duration for PostgreSQL servers with logging duration disabled." + tags = merge(local.postgresql_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + name = string + resource_group = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.postgresql_servers_with_log_duration_disabled_default_action + enum = local.postgresql_servers_with_log_duration_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.postgresql_servers_with_log_duration_disabled_enabled_actions + enum = local.postgresql_servers_with_log_duration_disabled_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} PostgreSQL server(s) with logging duration disabled." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.id => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_postgresql_server_with_log_duration_disabled + args = { + title = each.value.title + name = each.value.name + resource_group = each.value.resource_group + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_postgresql_server_with_log_duration_disabled" { + title = "Correct PostgreSQL server with logging duration disabled" + description = "Enable logging duration for a PostgreSQL server with logging duration disabled." + tags = merge(local.postgresql_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "name" { + type = string + description = "The name of the PostgreSQL server." + } + + param "resource_group" { + type = string + description = local.description_resource_group + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.postgresql_servers_with_log_duration_disabled_default_action + enum = local.postgresql_servers_with_log_duration_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.postgresql_servers_with_log_duration_disabled_enabled_actions + enum = local.postgresql_servers_with_log_duration_disabled_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected PostgreSQL server ${param.title} with logging duration disabled." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped PostgreSQL server ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "enable_logging_duration" = { + label = "Enable logging duration" + value = "enable_logging_duration" + style = local.style_alert + pipeline_ref = azure.pipeline.set_postgres_server_configuration + pipeline_args = { + server_name = param.name + resource_group = param.resource_group + subscription_id = param.subscription_id + conn = param.conn + config_name = "log_duration" + config_value = "on" + } + success_msg = "Enabled logging duration for PostgreSQL server ${param.title}." + error_msg = "Error enabling logging duration for PostgreSQL server ${param.title}." + } + } + } + } +} diff --git a/pipelines/postgresql/postgresql_servers_with_ssl_disabled.fp b/pipelines/postgresql/postgresql_servers_with_ssl_disabled.fp new file mode 100644 index 0000000..360b594 --- /dev/null +++ b/pipelines/postgresql/postgresql_servers_with_ssl_disabled.fp @@ -0,0 +1,323 @@ +locals { + postgresql_servers_with_ssl_disabled_query = <<-EOQ + select + concat(db.id, ' [', db.subscription_id, '/', db.resource_group, ']') as title, + db.id as id, + db.name, + db.resource_group, + db.subscription_id, + db._ctx ->> 'connection_name' as conn + from + azure_postgresql_server as db, + azure_subscription as sub + where + ssl_enforcement = 'Disabled' + and sub.subscription_id = db.subscription_id; + EOQ + + postgresql_servers_with_ssl_disabled_enabled_actions_enum = ["skip", "enable_ssl"] + postgresql_servers_with_ssl_disabled_default_action_enum = ["notify", "skip", "enable_ssl"] +} + +variable "postgresql_servers_with_ssl_disabled_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +variable "postgresql_servers_with_ssl_disabled_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +variable "postgresql_servers_with_ssl_disabled_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +variable "postgresql_servers_with_ssl_disabled_enabled_actions" { + type = list(string) + description = "The list of enabled actions to provide to approvers for selection." + default = ["skip", "enable_ssl"] + + tags = { + folder = "Advanced/PostgreSQL" + } +} + +trigger "query" "detect_and_correct_postgresql_servers_with_ssl_disabled" { + title = "Detect & correct PostgreSQL servers with SSL disabled" + description = "Detect PostgreSQL servers with SSL disabled and then enable SSL." + tags = local.postgresql_common_tags + + enabled = var.postgresql_servers_with_ssl_disabled_trigger_enabled + schedule = var.postgresql_servers_with_ssl_disabled_trigger_schedule + database = var.database + sql = local.postgresql_servers_with_ssl_disabled_query + + capture "insert" { + pipeline = pipeline.correct_postgresql_servers_with_ssl_disabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_postgresql_servers_with_ssl_disabled" { + title = "Detect & correct PostgreSQL servers with SSL disabled" + description = "Detect PostgreSQL servers with SSL disabled and then enable SSL." + tags = merge(local.postgresql_common_tags, { recommended = "true" }) + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.postgresql_servers_with_ssl_disabled_default_action + enum = local.postgresql_servers_with_ssl_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.postgresql_servers_with_ssl_disabled_enabled_actions + enum = local.postgresql_servers_with_ssl_disabled_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.postgresql_servers_with_ssl_disabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_postgresql_servers_with_ssl_disabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_postgresql_servers_with_ssl_disabled" { + title = "Correct PostgreSQL servers with SSL disabled" + description = "Enable SSL for PostgreSQL servers with SSL disabled." + tags = merge(local.postgresql_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + name = string + resource_group = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.postgresql_servers_with_ssl_disabled_default_action + enum = local.postgresql_servers_with_ssl_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.postgresql_servers_with_ssl_disabled_enabled_actions + enum = local.postgresql_servers_with_ssl_disabled_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} PostgreSQL server(s) with SSL disabled." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.id => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_postgresql_server_with_ssl_disabled + args = { + title = each.value.title + name = each.value.name + resource_group = each.value.resource_group + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_postgresql_server_with_ssl_disabled" { + title = "Correct PostgreSQL server with SSL disabled" + description = "Enable SSL for a PostgreSQL server with SSL disabled" + tags = merge(local.postgresql_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "name" { + type = string + description = "The name of the PostgreSQL server." + } + + param "resource_group" { + type = string + description = local.description_resource_group + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.postgresql_servers_with_ssl_disabled_default_action + enum = local.postgresql_servers_with_ssl_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.postgresql_servers_with_ssl_disabled_enabled_actions + enum = local.postgresql_servers_with_ssl_disabled_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected PostgreSQL server ${param.title} with SSL disabled." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped PostgreSQL server ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "enable_ssl" = { + label = "Enable SSL" + value = "enable_ssl" + style = local.style_alert + pipeline_ref = azure.pipeline.update_postgres_server_ssl_enforcement + pipeline_args = { + server_name = param.name + resource_group = param.resource_group + subscription_id = param.subscription_id + conn = param.conn + ssl_enforcement = "Enabled" + } + success_msg = "Enabled SSL for PostgreSQL server ${param.title}." + error_msg = "Error enabling SSL for PostgreSQL server ${param.title}." + } + } + } + } +} + + diff --git a/pipelines/redis/redis.fp b/pipelines/redis/redis.fp new file mode 100644 index 0000000..4a5991b --- /dev/null +++ b/pipelines/redis/redis.fp @@ -0,0 +1,5 @@ +locals { + redis_common_tags = merge(local.azure_compliance_common_tags, { + service = "Azure/Redis" + }) +} diff --git a/pipelines/redis/redis_caches_with_basic_sku.fp b/pipelines/redis/redis_caches_with_basic_sku.fp new file mode 100644 index 0000000..1c9fb1e --- /dev/null +++ b/pipelines/redis/redis_caches_with_basic_sku.fp @@ -0,0 +1,132 @@ +locals { + redis_caches_with_basic_sku_query = <<-EOQ + select + concat(id, ' [', subscription_id, '/', resource_group, ']') as title, + id as id, + name, + resource_group, + subscription_id, + _ctx ->> 'connection_name' as conn + from + azure_redis_cache + where + sku_name = 'Basic'; + EOQ +} + +variable "redis_caches_with_basic_sku_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/Redis" + } +} + +variable "redis_caches_with_basic_sku_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/Redis" + } +} + +trigger "query" "detect_and_correct_redis_caches_with_basic_sku" { + title = "Detect & correct Redis Caches with basic SKU" + description = "Detect Redis Caches with basic SKU." + tags = local.redis_common_tags + + enabled = var.redis_caches_with_basic_sku_trigger_enabled + schedule = var.redis_caches_with_basic_sku_trigger_schedule + database = var.database + sql = local.redis_caches_with_basic_sku_query + + capture "insert" { + pipeline = pipeline.correct_redis_caches_with_basic_sku + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_redis_caches_with_basic_sku" { + title = "Detect & correct Redis Caches with basic SKU" + description = "Detect Redis Caches with basic SKU." + tags = local.redis_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.redis_caches_with_basic_sku_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_redis_caches_with_basic_sku + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_redis_caches_with_basic_sku" { + title = "Correct Redis Cache using basic SKU" + description = "Send notifications for a Redis Cache using basic SKU." + tags = merge(local.redis_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} Redis Cache(s) using basic SKU." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected Redis Cache ${each.value.title} using basic SKU." + } +} diff --git a/pipelines/securitycenter/securitycenter_settings_without_mcas_integration.fp b/pipelines/securitycenter/securitycenter_settings_without_mcas_integration.fp new file mode 100644 index 0000000..4ca271f --- /dev/null +++ b/pipelines/securitycenter/securitycenter_settings_without_mcas_integration.fp @@ -0,0 +1,132 @@ +locals { + securitycenter_settings_without_mcas_integration_query = <<-EOQ + select + concat(sc_sett.id, ' [', sc_sett.subscription_id, ']') as title, + sc_sett.subscription_id, + sc_sett._ctx ->> 'connection_name' as conn, + enabled + from + azure_security_center_setting sc_sett + right join azure_subscription sub on sc_sett.subscription_id = sub.subscription_id + where + name = 'MCAS' + and (not enabled or enabled is null); + EOQ +} + +variable "securitycenter_settings_without_mcas_integration_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +variable "securitycenter_settings_without_mcas_integration_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +trigger "query" "detect_and_correct_securitycenter_settings_without_mcas_integration" { + title = "Detect & correct Security Center settings without MCAS integration" + description = "Detect Security Center settings without MCAS integration." + tags = local.securitycenter_common_tags + + enabled = var.securitycenter_settings_without_mcas_integration_trigger_enabled + schedule = var.securitycenter_settings_without_mcas_integration_trigger_schedule + database = var.database + sql = local.securitycenter_settings_without_mcas_integration_query + + capture "insert" { + pipeline = pipeline.correct_securitycenter_settings_without_mcas_integration + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_securitycenter_settings_without_mcas_integration" { + title = "Detect & correct Security Center settings without MCAS integration" + description = "Detect Security Center settings without MCAS integration." + tags = local.securitycenter_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.securitycenter_settings_without_mcas_integration_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_securitycenter_settings_without_mcas_integration + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_securitycenter_settings_without_mcas_integration" { + title = "Correct Security Center settings without MCAS integration" + description = "Send notifications for Security Center settings without MCAS integration." + tags = merge(local.securitycenter_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} Security Center setting(s) without MCAS integration." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected Security Center setting ${each.value.title} without MCAS integration." + } +} diff --git a/pipelines/securitycenter/securitycenter_settings_without_wdatp_integration.fp b/pipelines/securitycenter/securitycenter_settings_without_wdatp_integration.fp new file mode 100644 index 0000000..74421f1 --- /dev/null +++ b/pipelines/securitycenter/securitycenter_settings_without_wdatp_integration.fp @@ -0,0 +1,132 @@ +locals { + securitycenter_settings_without_wdatp_integration_query = <<-EOQ + select + concat(sc_sett.id, ' [', sc_sett.subscription_id, ']') as title, + sc_sett.subscription_id, + sc_sett._ctx ->> 'connection_name' as conn, + enabled + from + azure_security_center_setting sc_sett + right join azure_subscription sub on sc_sett.subscription_id = sub.subscription_id + where + name = 'WDATP' + and (not enabled or enabled is null); + EOQ +} + +variable "securitycenter_settings_without_wdatp_integration_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +variable "securitycenter_settings_without_wdatp_integration_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +trigger "query" "detect_and_correct_securitycenter_settings_without_wdatp_integration" { + title = "Detect & correct Security Center settings without WDATP integration" + description = "Detect Security Center settings without WDATP integration." + tags = local.securitycenter_common_tags + + enabled = var.securitycenter_settings_without_wdatp_integration_trigger_enabled + schedule = var.securitycenter_settings_without_wdatp_integration_trigger_schedule + database = var.database + sql = local.securitycenter_settings_without_wdatp_integration_query + + capture "insert" { + pipeline = pipeline.correct_securitycenter_settings_without_wdatp_integration + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_securitycenter_settings_without_wdatp_integration" { + title = "Detect & correct Security Center settings without WDATP integration" + description = "Detect Security Center settings without WDATP integration." + tags = local.securitycenter_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.securitycenter_settings_without_wdatp_integration_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_securitycenter_settings_without_wdatp_integration + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_securitycenter_settings_without_wdatp_integration" { + title = "Correct Security Center settings without WDATP integration" + description = "Send notifications for Security Center settings without WDATP integration." + tags = merge(local.securitycenter_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} Security Center setting(s) without WDATP integration." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected Security Center setting ${each.value.title} without WDATP integration." + } +} diff --git a/pipelines/securitycenter/securitycenters_with_automatic_provisioning_monitoring_agent_disabled.fp b/pipelines/securitycenter/securitycenters_with_automatic_provisioning_monitoring_agent_disabled.fp new file mode 100644 index 0000000..a93c6b8 --- /dev/null +++ b/pipelines/securitycenter/securitycenters_with_automatic_provisioning_monitoring_agent_disabled.fp @@ -0,0 +1,128 @@ +locals { + securitycenters_with_automatic_provisioning_monitoring_agent_disabled_query = <<-EOQ + select + concat(name, ' [', '/', subscription_id, ']') as title, + _ctx ->> 'connection_name' as conn + from + azure_security_center_auto_provisioning + where + auto_provision <> 'On' + EOQ +} + +variable "securitycenters_with_automatic_provisioning_monitoring_agent_disabled_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +variable "securitycenters_with_automatic_provisioning_monitoring_agent_disabled_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +trigger "query" "detect_and_correct_securitycenters_with_automatic_provisioning_monitoring_agent_disabled" { + title = "Detect & correct Security Center auto provisioning settings with automatic provisioning monitoring agent disabled" + description = "Detect Security Center auto provisioning settings with automatic provisioning monitoring agent disabled." + tags = local.securitycenter_common_tags + + enabled = var.securitycenters_with_automatic_provisioning_monitoring_agent_disabled_trigger_enabled + schedule = var.securitycenters_with_automatic_provisioning_monitoring_agent_disabled_trigger_schedule + database = var.database + sql = local.securitycenters_with_automatic_provisioning_monitoring_agent_disabled_query + + capture "insert" { + pipeline = pipeline.correct_securitycenters_with_automatic_provisioning_monitoring_agent_disabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_securitycenters_with_automatic_provisioning_monitoring_agent_disabled" { + title = "Detect & correct Security Center auto provisioning settings with automatic provisioning monitoring agent disabled" + description = "Detect Security Center auto provisioning settings with automatic provisioning monitoring agent disabled." + tags = local.securitycenter_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.securitycenters_with_automatic_provisioning_monitoring_agent_disabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_securitycenters_with_automatic_provisioning_monitoring_agent_disabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_securitycenters_with_automatic_provisioning_monitoring_agent_disabled" { + title = "Correct Security Center auto provisioning settings with automatic provisioning monitoring agent disabled" + description = "Send notifications for Security Center auto provisioning settings with automatic provisioning monitoring agent disabled." + tags = merge(local.securitycenter_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} Security Center auto provisioning setting(s) with automatic provisioning monitoring agent disabled." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected Security Center auto provisioning setting ${each.value.title} with automatic provisioning monitoring agent disabled." + } +} diff --git a/pipelines/securitycenter/securitycenters_with_azure_defender_for_app_service_disabled.fp b/pipelines/securitycenter/securitycenters_with_azure_defender_for_app_service_disabled.fp new file mode 100644 index 0000000..4244f42 --- /dev/null +++ b/pipelines/securitycenter/securitycenters_with_azure_defender_for_app_service_disabled.fp @@ -0,0 +1,313 @@ +locals { + securitycenters_with_azure_defender_for_app_service_disabled_query = <<-EOQ + select + concat(sc.id, ' [', '/', sc.subscription_id, ']') as title, + sc.id as id, + sc.name, + sc.subscription_id, + sc._ctx ->> 'connection_name' as conn + from + azure_security_center_subscription_pricing as sc, + azure_subscription as sub + where + sc.pricing_tier != 'Standard' + and sc.name = 'AppServices' + and sub.subscription_id = sc.subscription_id; + EOQ + + securitycenters_with_azure_defender_for_app_service_disabled_enabled_actions_enum = ["skip", "enable_app_service_azure_defender"] + securitycenters_with_azure_defender_for_app_service_disabled_default_action_enum = ["notify", "skip", "enable_app_service_azure_defender"] +} + +variable "securitycenters_with_azure_defender_for_app_service_disabled_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +variable "securitycenters_with_azure_defender_for_app_service_disabled_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +variable "securitycenters_with_azure_defender_for_app_service_disabled_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +variable "securitycenters_with_azure_defender_for_app_service_disabled_enabled_actions" { + type = list(string) + description = "The list of enabled actions to provide to approvers for selection." + default = ["skip", "enable_app_service_azure_defender"] + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +trigger "query" "detect_and_correct_securitycenters_with_azure_defender_for_app_service_disabled" { + title = "Detect & correct Security Centers with azure defender disabled for App Service" + description = "Detect Security Centers with azure defender disabled for App Service and then enable azure defender for App Service." + tags = local.securitycenter_common_tags + + enabled = var.securitycenters_with_azure_defender_for_app_service_disabled_trigger_enabled + schedule = var.securitycenters_with_azure_defender_for_app_service_disabled_trigger_schedule + database = var.database + sql = local.securitycenters_with_azure_defender_for_app_service_disabled_query + + capture "insert" { + pipeline = pipeline.correct_securitycenters_with_azure_defender_for_app_service_disabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_securitycenters_with_azure_defender_for_app_service_disabled" { + title = "Detect & correct Security Centers with azure defender disabled for App Service" + description = "Detect Security Centers with azure defender disabled for App Service and then enable azure defender for App Service." + tags = local.securitycenter_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.securitycenters_with_azure_defender_for_app_service_disabled_default_action + enum = local.securitycenters_with_azure_defender_for_app_service_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.securitycenters_with_azure_defender_for_app_service_disabled_enabled_actions + enum = local.securitycenters_with_azure_defender_for_app_service_disabled_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.securitycenters_with_azure_defender_for_app_service_disabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_securitycenters_with_azure_defender_for_app_service_disabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_securitycenters_with_azure_defender_for_app_service_disabled" { + title = "Correct Security Centers with azure defender disabled for App Service" + description = "Enable azure defender for App Service in Security Centers with azure defender disabled for App Service." + tags = merge(local.securitycenter_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + name = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.securitycenters_with_azure_defender_for_app_service_disabled_default_action + enum = local.securitycenters_with_azure_defender_for_app_service_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.securitycenters_with_azure_defender_for_app_service_disabled_enabled_actions + enum = local.securitycenters_with_azure_defender_for_app_service_disabled_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} Security Center(s) with azure defender disabled for App Service." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.id => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_securitycenter_with_azure_defender_for_app_service_disabled + args = { + title = each.value.title + name = each.value.name + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_securitycenter_with_azure_defender_for_app_service_disabled" { + title = "Correct Security Center with azure defender disabled for App Service" + description = "Enable azure defender for App Service in Security Center with azure defender disabled for App Service." + tags = merge(local.securitycenter_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "name" { + type = string + description = "The name of the security center subscription pricing." + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.securitycenters_with_azure_defender_for_app_service_disabled_default_action + enum = local.securitycenters_with_azure_defender_for_app_service_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.securitycenters_with_azure_defender_for_app_service_disabled_enabled_actions + enum = local.securitycenters_with_azure_defender_for_app_service_disabled_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected Security Center ${param.title} with azure defender disabled for App Service." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped Security Center ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "enable_app_service_azure_defender" = { + label = "Enable App Service azure defender" + value = "enable_app_service_azure_defender" + style = local.style_alert + pipeline_ref = azure.pipeline.create_security_pricing + pipeline_args = { + resource_type = "AppServices" + subscription_id = param.subscription_id + conn = param.conn + tier = "Standard" + } + success_msg = "Enabled azure defender for App Service in Security Center ${param.title}." + error_msg = "Error enabling azure defender for App Service in Security Center ${param.title}." + } + } + } + } +} diff --git a/pipelines/securitycenter/securitycenters_with_azure_defender_for_container_disabled.fp b/pipelines/securitycenter/securitycenters_with_azure_defender_for_container_disabled.fp new file mode 100644 index 0000000..aac1f23 --- /dev/null +++ b/pipelines/securitycenter/securitycenters_with_azure_defender_for_container_disabled.fp @@ -0,0 +1,314 @@ +locals { + securitycenters_with_azure_defender_for_container_disabled_query = <<-EOQ + select + concat(sc.id, ' [', '/', sc.subscription_id, ']') as title, + sc.id as id, + sc.name, + sc.subscription_id, + sc._ctx ->> 'connection_name' as conn + from + azure_security_center_subscription_pricing as sc, + azure_subscription as sub + where + sc.pricing_tier != 'Standard' + and sc.name = 'Containers' + and sub.subscription_id = sc.subscription_id; + EOQ + + securitycenters_with_azure_defender_for_container_disabled_enabled_actions_enum = ["skip", "enable_container_azure_defender"] + securitycenters_with_azure_defender_for_container_disabled_default_action_enum = ["notify", "skip", "enable_container_azure_defender"] +} + +variable "securitycenters_with_azure_defender_for_container_disabled_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +variable "securitycenters_with_azure_defender_for_container_disabled_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +variable "securitycenters_with_azure_defender_for_container_disabled_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +variable "securitycenters_with_azure_defender_for_container_disabled_enabled_actions" { + type = list(string) + description = "The list of enabled actions to provide to approvers for selection." + default = ["skip", "enable_container_azure_defender"] + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +trigger "query" "detect_and_correct_securitycenters_with_azure_defender_for_container_disabled" { + title = "Detect & correct Security Centers with azure defender disabled for Container" + description = "Detect Security Centers with azure defender disabled for Container and then enable azure defender for Container." + tags = local.securitycenter_common_tags + + enabled = var.securitycenters_with_azure_defender_for_container_disabled_trigger_enabled + schedule = var.securitycenters_with_azure_defender_for_container_disabled_trigger_schedule + database = var.database + sql = local.securitycenters_with_azure_defender_for_container_disabled_query + + capture "insert" { + pipeline = pipeline.correct_securitycenters_with_azure_defender_for_container_disabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_securitycenters_with_azure_defender_for_container_disabled" { + title = "Detect & correct Security Centers with azure defender disabled for Container" + description = "Detect Security Centers with azure defender disabled for Container and then enable azure defender for Container." + tags = local.securitycenter_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.securitycenters_with_azure_defender_for_container_disabled_default_action + enum = local.securitycenters_with_azure_defender_for_container_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.securitycenters_with_azure_defender_for_container_disabled_enabled_actions + enum = local.securitycenters_with_azure_defender_for_container_disabled_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.securitycenters_with_azure_defender_for_container_disabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_securitycenters_with_azure_defender_for_container_disabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_securitycenters_with_azure_defender_for_container_disabled" { + title = "Correct Security Centers with azure defender disabled for Container" + description = "Enable azure defender for Container in Security Centers with azure defender disabled for Container." + tags = merge(local.securitycenter_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + name = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.securitycenters_with_azure_defender_for_container_disabled_default_action + enum = local.securitycenters_with_azure_defender_for_container_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.securitycenters_with_azure_defender_for_container_disabled_enabled_actions + enum = local.securitycenters_with_azure_defender_for_container_disabled_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} Security Center(s) with azure defender disabled for Container." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.id => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_securitycenter_with_azure_defender_for_containers_disabled + args = { + title = each.value.title + name = each.value.name + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_securitycenter_with_azure_defender_for_containers_disabled" { + title = "Correct Security Center with azure defender disabled for Container" + description = "Enable azure defender for Container in Security Center with azure defender disabled for Container." + tags = merge(local.securitycenter_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "name" { + type = string + description = "The name of the security center subscription pricing." + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.securitycenters_with_azure_defender_for_container_disabled_default_action + enum = local.securitycenters_with_azure_defender_for_container_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.securitycenters_with_azure_defender_for_container_disabled_enabled_actions + enum = local.securitycenters_with_azure_defender_for_container_disabled_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected Security Center ${param.title} with azure defender disabled for Container." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped Security Center ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "enable_container_azure_defender" = { + label = "Enable Container azure defender" + value = "enable_container_azure_defender" + style = local.style_alert + pipeline_ref = azure.pipeline.create_security_pricing + pipeline_args = { + resource_type = "Container" + subscription_id = param.subscription_id + conn = param.conn + tier = "Standard" + } + success_msg = "Enabled azure defender for Container in Security Center ${param.title}." + error_msg = "Error enabling azure defender for Container in Security Center ${param.title}." + } + } + } + } +} + diff --git a/pipelines/securitycenter/securitycenters_with_azure_defender_for_container_registry_disabled.fp b/pipelines/securitycenter/securitycenters_with_azure_defender_for_container_registry_disabled.fp new file mode 100644 index 0000000..989b4b0 --- /dev/null +++ b/pipelines/securitycenter/securitycenters_with_azure_defender_for_container_registry_disabled.fp @@ -0,0 +1,313 @@ +locals { + securitycenters_with_azure_defender_for_container_registry_disabled_query = <<-EOQ + select + concat(sc.id, ' [', '/', sc.subscription_id, ']') as title, + sc.id as id, + sc.name, + sc.subscription_id, + sc._ctx ->> 'connection_name' as conn + from + azure_security_center_subscription_pricing as sc, + azure_subscription as sub + where + sc.pricing_tier != 'Standard' + and sc.name = 'ContainerRegistry' + and sub.subscription_id = sc.subscription_id; + EOQ + + securitycenters_with_azure_defender_for_container_registry_disabled_enabled_actions_enum = ["skip", "enable_container_registry_azure_defender"] + securitycenters_with_azure_defender_for_container_registry_disabled_default_action_enum = ["notify", "skip", "enable_container_registry_azure_defender"] +} + +variable "securitycenters_with_azure_defender_for_container_registry_disabled_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +variable "securitycenters_with_azure_defender_for_container_registry_disabled_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +variable "securitycenters_with_azure_defender_for_container_registry_disabled_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +variable "securitycenters_with_azure_defender_for_container_registry_disabled_enabled_actions" { + type = list(string) + description = "The list of enabled actions to provide to approvers for selection." + default = ["skip", "enable_container_registry_azure_defender"] + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +trigger "query" "detect_and_correct_securitycenters_with_azure_defender_for_container_registry_disabled" { + title = "Detect & correct Security Centers with Azure Defender disabled for Container Registry" + description = "Detect Security Centers with Azure Defender disabled for Container Registry and then enable Azure Defender for Container Registry." + tags = local.securitycenter_common_tags + + enabled = var.securitycenters_with_azure_defender_for_container_registry_disabled_trigger_enabled + schedule = var.securitycenters_with_azure_defender_for_container_registry_disabled_trigger_schedule + database = var.database + sql = local.securitycenters_with_azure_defender_for_container_registry_disabled_query + + capture "insert" { + pipeline = pipeline.correct_securitycenters_with_azure_defender_for_container_registry_disabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_securitycenters_with_azure_defender_for_container_registry_disabled" { + title = "Detect & correct Security Centers with Azure Defender disabled for Container Registry" + description = "Detect Security Centers with Azure Defender disabled for Container Registry and then enable Azure Defender for Container Registry." + tags = local.securitycenter_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.securitycenters_with_azure_defender_for_container_registry_disabled_default_action + enum = local.securitycenters_with_azure_defender_for_container_registry_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.securitycenters_with_azure_defender_for_container_registry_disabled_enabled_actions + enum = local.securitycenters_with_azure_defender_for_container_registry_disabled_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.securitycenters_with_azure_defender_for_container_registry_disabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_securitycenters_with_azure_defender_for_container_registry_disabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_securitycenters_with_azure_defender_for_container_registry_disabled" { + title = "Correct Security Centers with Azure Defender disabled for Container Registry" + description = "Enable Azure Defender for Container Registry in Security Centers with Azure Defender disabled for Container Registry." + tags = merge(local.securitycenter_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + name = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.securitycenters_with_azure_defender_for_container_registry_disabled_default_action + enum = local.securitycenters_with_azure_defender_for_container_registry_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.securitycenters_with_azure_defender_for_container_registry_disabled_enabled_actions + enum = local.securitycenters_with_azure_defender_for_container_registry_disabled_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected Security Center Azure Defender turned off for Container Registries." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.id => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_securitycenter_with_azure_defender_for_container_registry_disabled + args = { + title = each.value.title + name = each.value.name + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_securitycenter_with_azure_defender_for_container_registry_disabled" { + title = "Correct Security Center with Azure Defender disabled for Container Registry" + description = "Enable Azure Defender for Container Registry in Security Center with Azure Defender disabled for Container Registry." + tags = merge(local.securitycenter_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "name" { + type = string + description = "The name of the security center subscription pricing." + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.securitycenters_with_azure_defender_for_container_registry_disabled_default_action + enum = local.securitycenters_with_azure_defender_for_container_registry_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.securitycenters_with_azure_defender_for_container_registry_disabled_enabled_actions + enum = local.securitycenters_with_azure_defender_for_container_registry_disabled_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected Security Center ${param.title} with Azure Defender disabled for Container Registry." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped Security Center ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "enable_container_registry_azure_defender" = { + label = "Enable container registry Azure Defender" + value = "enable_container_registry_azure_defender" + style = local.style_alert + pipeline_ref = azure.pipeline.create_security_pricing + pipeline_args = { + resource_type = "ContainerRegistry" + subscription_id = param.subscription_id + conn = param.conn + tier = "Standard" + } + success_msg = "Enabled Azure Defender for Container Registry in Security Center ${param.title}." + error_msg = "Error enabling Azure Defender for Container Registry in Security Center ${param.title}." + } + } + } + } +} diff --git a/pipelines/securitycenter/securitycenters_with_azure_defender_for_cosmosdb_disabled.fp b/pipelines/securitycenter/securitycenters_with_azure_defender_for_cosmosdb_disabled.fp new file mode 100644 index 0000000..787f393 --- /dev/null +++ b/pipelines/securitycenter/securitycenters_with_azure_defender_for_cosmosdb_disabled.fp @@ -0,0 +1,313 @@ +locals { + securitycenters_with_azure_defender_for_cosmosdb_disabled_query = <<-EOQ + select + concat(sc.id, ' [', '/', sc.subscription_id, ']') as title, + sc.id as id, + sc.name, + sc.subscription_id, + sc._ctx ->> 'connection_name' as conn + from + azure_security_center_subscription_pricing as sc, + azure_subscription as sub + where + sc.pricing_tier != 'Standard' + and sc.name = 'CosmosDbs' + and sub.subscription_id = sc.subscription_id; + EOQ + + securitycenters_with_azure_defender_for_cosmosdb_disabled_enabled_actions_enum = ["skip", "enable_cosmosdb_azure_defender"] + securitycenters_with_azure_defender_for_cosmosdb_disabled_default_action_enum = ["notify", "skip", "enable_cosmosdb_azure_defender"] +} + +variable "securitycenters_with_azure_defender_for_cosmosdb_disabled_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +variable "securitycenters_with_azure_defender_for_cosmosdb_disabled_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +variable "securitycenters_with_azure_defender_for_cosmosdb_disabled_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +variable "securitycenters_with_azure_defender_for_cosmosdb_disabled_enabled_actions" { + type = list(string) + description = "The list of enabled actions to provide to approvers for selection." + default = ["skip", "enable_cosmosdb_azure_defender"] + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +trigger "query" "detect_and_correct_securitycenters_with_azure_defender_for_cosmosdb_disabled" { + title = "Detect & correct Security Centers with Azure Defender disabled for Cosmos DB" + description = "Detect Security Centers with Azure Defender disabled for Cosmos DB and then enable Azure Defender for Cosmos DB." + tags = local.securitycenter_common_tags + + enabled = var.securitycenters_with_azure_defender_for_cosmosdb_disabled_trigger_enabled + schedule = var.securitycenters_with_azure_defender_for_cosmosdb_disabled_trigger_schedule + database = var.database + sql = local.securitycenters_with_azure_defender_for_cosmosdb_disabled_query + + capture "insert" { + pipeline = pipeline.correct_securitycenters_with_azure_defender_for_cosmosdb_disabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_securitycenters_with_azure_defender_for_cosmosdb_disabled" { + title = "Detect & correct Security Centers with Azure Defender disabled for Cosmos DB" + description = "Detect Security Centers with Azure Defender disabled for Cosmos DB and then enable Azure Defender for Cosmos DB." + tags = local.securitycenter_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.securitycenters_with_azure_defender_for_cosmosdb_disabled_default_action + enum = local.securitycenters_with_azure_defender_for_cosmosdb_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.securitycenters_with_azure_defender_for_cosmosdb_disabled_enabled_actions + enum = local.securitycenters_with_azure_defender_for_cosmosdb_disabled_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.securitycenters_with_azure_defender_for_cosmosdb_disabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_securitycenters_with_azure_defender_for_cosmosdb_disabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_securitycenters_with_azure_defender_for_cosmosdb_disabled" { + title = "Correct Security Centers with Azure Defender disabled for Cosmos DB" + description = "Enable Azure Defender for Cosmos DB in Security Centers with Azure Defender disabled for Cosmos DB." + tags = merge(local.securitycenter_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + name = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.securitycenters_with_azure_defender_for_cosmosdb_disabled_default_action + enum = local.securitycenters_with_azure_defender_for_cosmosdb_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.securitycenters_with_azure_defender_for_cosmosdb_disabled_enabled_actions + enum = local.securitycenters_with_azure_defender_for_cosmosdb_disabled_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} Security Center(s) with Azure Defender disabled for Cosmos DB." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.id => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_securitycenter_with_azure_defender_for_cosmosdb_disabled + args = { + title = each.value.title + name = each.value.name + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_securitycenter_with_azure_defender_for_cosmosdb_disabled" { + title = "Correct Security Center with Azure Defender disabled for Cosmos DB" + description = "Enable Azure Defender for Cosmos DB in Security Center with Azure Defender disabled for Cosmos DB." + tags = merge(local.securitycenter_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "name" { + type = string + description = "The name of the security center subscription pricing." + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.securitycenters_with_azure_defender_for_cosmosdb_disabled_default_action + enum = local.securitycenters_with_azure_defender_for_cosmosdb_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.securitycenters_with_azure_defender_for_cosmosdb_disabled_enabled_actions + enum = local.securitycenters_with_azure_defender_for_cosmosdb_disabled_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected Security Center ${param.title} with Azure Defender disabled for Cosmos DB." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped Security Center ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "enable_cosmosdb_azure_defender" = { + label = "Enable Cosmos DB Azure Defender" + value = "enable_cosmosdb_azure_defender" + style = local.style_alert + pipeline_ref = azure.pipeline.create_security_pricing + pipeline_args = { + resource_type = "Cosmos DB" + subscription_id = param.subscription_id + conn = param.conn + tier = "Standard" + } + success_msg = "Enabled Azure Defender for Cosmos DB in Security Center ${param.title}." + error_msg = "Error enabling Azure Defender for Cosmos DB in Security Center ${param.title}." + } + } + } + } +} diff --git a/pipelines/securitycenter/securitycenters_with_azure_defender_for_dns_disabled.fp b/pipelines/securitycenter/securitycenters_with_azure_defender_for_dns_disabled.fp new file mode 100644 index 0000000..fb2d41d --- /dev/null +++ b/pipelines/securitycenter/securitycenters_with_azure_defender_for_dns_disabled.fp @@ -0,0 +1,314 @@ +locals { + securitycenters_with_azure_defender_for_dns_disabled_query = <<-EOQ + select + concat(sc.id, ' [', '/', sc.subscription_id, ']') as title, + sc.id as id, + sc.name, + sc.subscription_id, + sc._ctx ->> 'connection_name' as conn + from + azure_security_center_subscription_pricing as sc, + azure_subscription as sub + where + sc.pricing_tier != 'Standard' + and sc.name = 'Dns' + and sub.subscription_id = sc.subscription_id; + EOQ + + securitycenters_with_azure_defender_for_dns_disabled_enabled_actions_enum = ["skip", "enable_dns_azure_defender"] + securitycenters_with_azure_defender_for_dns_disabled_default_action_enum = ["notify", "skip", "enable_dns_azure_defender"] +} + +variable "securitycenters_with_azure_defender_for_dns_disabled_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +variable "securitycenters_with_azure_defender_for_dns_disabled_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +variable "securitycenters_with_azure_defender_for_dns_disabled_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +variable "securitycenters_with_azure_defender_for_dns_disabled_enabled_actions" { + type = list(string) + description = "The list of enabled actions to provide to approvers for selection." + default = ["skip", "enable_dns_azure_defender"] + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +trigger "query" "detect_and_correct_securitycenters_with_azure_defender_for_dns_disabled" { + title = "Detect & correct Security Centers with Azure Defender disabled for DNS" + description = "Detect Security Centers with Azure Defender disabled for DNS and then enable Azure Defender for DNS." + tags = local.securitycenter_common_tags + + enabled = var.securitycenters_with_azure_defender_for_dns_disabled_trigger_enabled + schedule = var.securitycenters_with_azure_defender_for_dns_disabled_trigger_schedule + database = var.database + sql = local.securitycenters_with_azure_defender_for_dns_disabled_query + + capture "insert" { + pipeline = pipeline.correct_securitycenters_with_azure_defender_for_dns_disabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_securitycenters_with_azure_defender_for_dns_disabled" { + title = "Detect & correct Security Centers with Azure Defender disabled for DNS" + description = "Detect Security Centers with Azure Defender disabled for DNS and then enable Azure Defender for DNS." + tags = local.securitycenter_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.securitycenters_with_azure_defender_for_dns_disabled_default_action + enum = local.securitycenters_with_azure_defender_for_dns_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.securitycenters_with_azure_defender_for_dns_disabled_enabled_actions + enum = local.securitycenters_with_azure_defender_for_dns_disabled_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.securitycenters_with_azure_defender_for_dns_disabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_securitycenters_with_azure_defender_for_dns_disabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_securitycenters_with_azure_defender_for_dns_disabled" { + title = "Correct Security Centers with Azure Defender disabled for DNS" + description = "Enable Azure Defender for DNS in Security Centers with Azure Defender disabled for DNS." + tags = merge(local.securitycenter_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + name = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.securitycenters_with_azure_defender_for_dns_disabled_default_action + enum = local.securitycenters_with_azure_defender_for_dns_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.securitycenters_with_azure_defender_for_dns_disabled_enabled_actions + enum = local.securitycenters_with_azure_defender_for_dns_disabled_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} Security Center(s) with Azure Defender disabled for DNS." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.id => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_securitycenter_with_azure_defender_for_dns_disabled + args = { + title = each.value.title + name = each.value.name + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_securitycenter_with_azure_defender_for_dns_disabled" { + title = "Correct Security Center with Azure Defender disabled for DNS" + description = "Enable Azure Defender for DNS in Security Center with Azure Defender disabled for DNS." + tags = merge(local.securitycenter_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "name" { + type = string + description = "The name of the security center subscription pricing." + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.securitycenters_with_azure_defender_for_dns_disabled_default_action + enum = local.securitycenters_with_azure_defender_for_dns_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.securitycenters_with_azure_defender_for_dns_disabled_enabled_actions + enum = local.securitycenters_with_azure_defender_for_dns_disabled_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected Security Center ${param.title} with Azure Defender disabled for DNS." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped Security Center ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "enable_dns_azure_defender" = { + label = "Enable DND Azure Defender" + value = "enable_dns_azure_defender" + style = local.style_alert + pipeline_ref = azure.pipeline.create_security_pricing + pipeline_args = { + resource_type = "Dns" + subscription_id = param.subscription_id + conn = param.conn + tier = "Standard" + } + success_msg = "Enabled Azure Defender for DNS in Security Center ${param.title}." + error_msg = "Error enabling Azure Defender for DNS in Security Center ${param.title}." + } + } + } + } +} + diff --git a/pipelines/securitycenter/securitycenters_with_azure_defender_for_keyvault_disabled.fp b/pipelines/securitycenter/securitycenters_with_azure_defender_for_keyvault_disabled.fp new file mode 100644 index 0000000..768ea2f --- /dev/null +++ b/pipelines/securitycenter/securitycenters_with_azure_defender_for_keyvault_disabled.fp @@ -0,0 +1,314 @@ +locals { + securitycenters_with_azure_defender_for_keyvault_disabled_query = <<-EOQ + select + concat(sc.id, ' [', '/', sc.subscription_id, ']') as title, + sc.id as id, + sc.name, + sc.subscription_id, + sc._ctx ->> 'connection_name' as conn + from + azure_security_center_subscription_pricing as sc, + azure_subscription as sub + where + sc.pricing_tier != 'Standard' + and sc.name = 'KeyVaults' + and sub.subscription_id = sc.subscription_id; + EOQ + + securitycenters_with_azure_defender_for_keyvault_disabled_enabled_actions_enum = ["skip", "enable_key_vault_azure_defender"] + securitycenters_with_azure_defender_for_keyvault_disabled_default_action_enum = ["notify", "skip", "enable_key_vault_azure_defender"] +} + +variable "securitycenters_with_azure_defender_for_keyvault_disabled_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +variable "securitycenters_with_azure_defender_for_keyvault_disabled_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +variable "securitycenters_with_azure_defender_for_keyvault_disabled_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +variable "securitycenters_with_azure_defender_for_keyvault_disabled_enabled_actions" { + type = list(string) + description = "The list of enabled actions to provide to approvers for selection." + default = ["skip", "enable_key_vault_azure_defender"] + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +trigger "query" "detect_and_correct_securitycenters_with_azure_defender_for_keyvault_disabled" { + title = "Detect & correct Security Centers with Azure Defender disabled for Key Vault" + description = "Detect Security Centers with Azure Defender disabled for Key Vault and then enable Azure Defender for Key Vault." + tags = local.securitycenter_common_tags + + enabled = var.securitycenters_with_azure_defender_for_keyvault_disabled_trigger_enabled + schedule = var.securitycenters_with_azure_defender_for_keyvault_disabled_trigger_schedule + database = var.database + sql = local.securitycenters_with_azure_defender_for_keyvault_disabled_query + + capture "insert" { + pipeline = pipeline.correct_securitycenters_with_azure_defender_for_keyvault_disabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_securitycenters_with_azure_defender_for_keyvault_disabled" { + title = "Detect & correct Security Centers with Azure Defender disabled for Key Vault" + description = "Detect Security Centers with Azure Defender disabled for Key Vault and then enable Azure Defender for Key Vault." + tags = local.securitycenter_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.securitycenters_with_azure_defender_for_keyvault_disabled_default_action + enum = local.securitycenters_with_azure_defender_for_keyvault_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.securitycenters_with_azure_defender_for_keyvault_disabled_enabled_actions + enum = local.securitycenters_with_azure_defender_for_keyvault_disabled_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.securitycenters_with_azure_defender_for_keyvault_disabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_securitycenters_with_azure_defender_for_keyvault_disabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_securitycenters_with_azure_defender_for_keyvault_disabled" { + title = "Correct Security Centers with Azure Defender disabled for Key Vault" + description = "Enable Azure Defender for Key Vault in Security Centers with Azure Defender disabled for Key Vault." + tags = merge(local.securitycenter_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + name = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.securitycenters_with_azure_defender_for_keyvault_disabled_default_action + enum = local.securitycenters_with_azure_defender_for_keyvault_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.securitycenters_with_azure_defender_for_keyvault_disabled_enabled_actions + enum = local.securitycenters_with_azure_defender_for_keyvault_disabled_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} Security Center(s) with Azure Defender disabled for Key Vault." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.id => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_securitycenter_with_azure_defender_for_keyvault_disabled + args = { + title = each.value.title + name = each.value.name + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_securitycenter_with_azure_defender_for_keyvault_disabled" { + title = "Correct Security Center with Azure Defender disabled for Key Vault" + description = "Enable Azure Defender for Key Vault in Security Center with Azure Defender disabled for Key Vault." + tags = merge(local.securitycenter_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "name" { + type = string + description = "The name of the security center subscription pricing." + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.securitycenters_with_azure_defender_for_keyvault_disabled_default_action + enum = local.securitycenters_with_azure_defender_for_keyvault_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.securitycenters_with_azure_defender_for_keyvault_disabled_enabled_actions + enum = local.securitycenters_with_azure_defender_for_keyvault_disabled_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected Security Center ${param.title} with Azure Defender disabled for Key Vault." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped Security Center ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "enable_key_vault_azure_defender" = { + label = "Enable Key Vault Azure Defender" + value = "enable_key_vault_azure_defender" + style = local.style_alert + pipeline_ref = azure.pipeline.create_security_pricing + pipeline_args = { + resource_type = "KeyVaults" + subscription_id = param.subscription_id + conn = param.conn + tier = "Standard" + } + success_msg = "Enabled Azure Defender for Key Vault in Security Center ${param.title}." + error_msg = "Error enabling Azure Defender for Key Vault in Security Center ${param.title}." + } + } + } + } +} + diff --git a/pipelines/securitycenter/securitycenters_with_azure_defender_for_open_source_relational_db_disabled.fp b/pipelines/securitycenter/securitycenters_with_azure_defender_for_open_source_relational_db_disabled.fp new file mode 100644 index 0000000..48220e4 --- /dev/null +++ b/pipelines/securitycenter/securitycenters_with_azure_defender_for_open_source_relational_db_disabled.fp @@ -0,0 +1,315 @@ +locals { + securitycenters_with_azure_defender_for_open_source_relational_db_disabled_query = <<-EOQ + select + concat(sc.id, ' [', '/', sc.subscription_id, ']') as title, + sc.id as id, + sc.name, + sc.subscription_id, + sc._ctx ->> 'connection_name' as conn + from + azure_security_center_subscription_pricing as sc, + azure_subscription as sub + where + sc.pricing_tier != 'Standard' + and sc.name = 'OpenSourceRelationalDatabases' + and sub.subscription_id = sc.subscription_id; + EOQ + + securitycenters_with_azure_defender_for_open_source_relational_db_disabled_enabled_actions_enum = ["skip", "enable_open_source_relational_db_azure_defender"] + securitycenters_with_azure_defender_for_open_source_relational_db_disabled_default_action_enum = ["notify", "skip", "enable_open_source_relational_db_azure_defender"] +} + +variable "securitycenters_with_azure_defender_for_open_source_relational_db_disabled_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +variable "securitycenters_with_azure_defender_for_open_source_relational_db_disabled_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +variable "securitycenters_with_azure_defender_for_open_source_relational_db_disabled_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +variable "securitycenters_with_azure_defender_for_open_source_relational_db_disabled_enabled_actions" { + type = list(string) + description = "The list of enabled actions to provide to approvers for selection." + default = ["skip", "enable_open_source_relational_db_azure_defender"] + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +trigger "query" "detect_and_correct_securitycenters_with_azure_defender_for_open_source_relational_db_disabled" { + title = "Detect & correct Security Centers with Azure Defender disabled for open-source relational database" + description = "Detect Security Centers with Azure Defender disabled for open-source relational database and then enable Azure Defender for open-source relational database." + tags = local.securitycenter_common_tags + + enabled = var.securitycenters_with_azure_defender_for_open_source_relational_db_disabled_trigger_enabled + schedule = var.securitycenters_with_azure_defender_for_open_source_relational_db_disabled_trigger_schedule + database = var.database + sql = local.securitycenters_with_azure_defender_for_open_source_relational_db_disabled_query + + capture "insert" { + pipeline = pipeline.correct_securitycenters_with_azure_defender_for_open_source_relational_db_disabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_securitycenters_with_azure_defender_for_open_source_relational_db_disabled" { + title = "Detect & correct Security Centers with Azure Defender disabled for open-source relational database" + description = "Detect Security Centers with Azure Defender disabled for open-source relational database and then enable Azure Defender for open-source relational database." + tags = local.securitycenter_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.securitycenters_with_azure_defender_for_open_source_relational_db_disabled_default_action + enum = local.securitycenters_with_azure_defender_for_open_source_relational_db_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.securitycenters_with_azure_defender_for_open_source_relational_db_disabled_enabled_actions + enum = local.securitycenters_with_azure_defender_for_open_source_relational_db_disabled_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.securitycenters_with_azure_defender_for_open_source_relational_db_disabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_securitycenters_with_azure_defender_for_open_source_relational_db_disabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_securitycenters_with_azure_defender_for_open_source_relational_db_disabled" { + title = "Correct Security Centers with Azure Defender disabled for open-source relational database" + description = "Enable Azure Defender for open-source relational database in Security Centers with Azure Defender disabled for open-source relational database." + tags = merge(local.securitycenter_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + name = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.securitycenters_with_azure_defender_for_open_source_relational_db_disabled_default_action + enum = local.securitycenters_with_azure_defender_for_open_source_relational_db_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.securitycenters_with_azure_defender_for_open_source_relational_db_disabled_enabled_actions + enum = local.securitycenters_with_azure_defender_for_open_source_relational_db_disabled_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} Security Center(s) with Azure Defender disabled for open-source relational database." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.id => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_securitycenter_with_azure_defender_for_open_source_relational_db_disabled + args = { + title = each.value.title + name = each.value.name + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + + +pipeline "correct_one_securitycenter_with_azure_defender_for_open_source_relational_db_disabled" { + title = "Correct Security Center with Azure Defender disabled for open-source relational database" + description = "Enable Azure Defender for open-source relational database in Security Center with Azure Defender disabled for open-source relational database." + tags = merge(local.securitycenter_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "name" { + type = string + description = "The name of the security center subscription pricing." + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.securitycenters_with_azure_defender_for_open_source_relational_db_disabled_default_action + enum = local.securitycenters_with_azure_defender_for_open_source_relational_db_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.securitycenters_with_azure_defender_for_open_source_relational_db_disabled_enabled_actions + enum = local.securitycenters_with_azure_defender_for_open_source_relational_db_disabled_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected Security Center ${param.title} with Azure Defender disabled for open-source relational database." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped Security Center ${param.title} " + } + success_msg = "" + error_msg = "" + }, + "enable_open_source_relational_db_azure_defender" = { + label = "Enable open-source relational database Azure Defender" + value = "enable_open_source_relational_db_azure_defender" + style = local.style_alert + pipeline_ref = azure.pipeline.create_security_pricing + pipeline_args = { + resource_type = "OpenSourceRelationalDatabases" + subscription_id = param.subscription_id + conn = param.conn + tier = "Standard" + } + success_msg = "Enabled Azure Defender for open-source relational database in Security Center ${param.title}." + error_msg = "Error enabling Azure Defender for open-source relational database in Security Center ${param.title}." + } + } + } + } +} + diff --git a/pipelines/securitycenter/securitycenters_with_azure_defender_for_resource_manager_disabled.fp b/pipelines/securitycenter/securitycenters_with_azure_defender_for_resource_manager_disabled.fp new file mode 100644 index 0000000..2126319 --- /dev/null +++ b/pipelines/securitycenter/securitycenters_with_azure_defender_for_resource_manager_disabled.fp @@ -0,0 +1,313 @@ +locals { + securitycenters_with_azure_defender_for_resource_manager_disabled_query = <<-EOQ + select + concat(sc.id, ' [', '/', sc.subscription_id, ']') as title, + sc.id as id, + sc.name, + sc.subscription_id, + sc._ctx ->> 'connection_name' as conn + from + azure_security_center_subscription_pricing as sc, + azure_subscription as sub + where + sc.pricing_tier != 'Standard' + and sc.name = 'Arm' + and sub.subscription_id = sc.subscription_id; + EOQ + + securitycenters_with_azure_defender_for_resource_manager_disabled_enabled_actions_enum = ["skip", "enable_resource_manager_azure_defender"] + securitycenters_with_azure_defender_for_resource_manager_disabled_default_action_enum = ["notify", "skip", "enable_resource_manager_azure_defender"] +} + +variable "securitycenters_with_azure_defender_for_resource_manager_disabled_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +variable "securitycenters_with_azure_defender_for_resource_manager_disabled_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +variable "securitycenters_with_azure_defender_for_resource_manager_disabled_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +variable "securitycenters_with_azure_defender_for_resource_manager_disabled_enabled_actions" { + type = list(string) + description = "The list of enabled actions to provide to approvers for selection." + default = ["skip", "enable_resource_manager_azure_defender"] + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +trigger "query" "detect_and_correct_securitycenters_with_azure_defender_for_resource_manager_disabled" { + title = "Detect & correct Security Centers with Azure Defender disabled for Resource Manager" + description = "Detect Security Centers with Azure Defender disabled for Resource Manager and then enable Azure Defender for Resource Manager." + tags = local.securitycenter_common_tags + + enabled = var.securitycenters_with_azure_defender_for_resource_manager_disabled_trigger_enabled + schedule = var.securitycenters_with_azure_defender_for_resource_manager_disabled_trigger_schedule + database = var.database + sql = local.securitycenters_with_azure_defender_for_resource_manager_disabled_query + + capture "insert" { + pipeline = pipeline.correct_securitycenters_with_azure_defender_for_resource_manager_disabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_securitycenters_with_azure_defender_for_resource_manager_disabled" { + title = "Detect & correct Security Centers with Azure Defender disabled for Resource Manager" + description = "Detect Security Centers with Azure Defender disabled for Resource Manager and then enable Azure Defender for Resource Manager." + tags = local.securitycenter_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.securitycenters_with_azure_defender_for_resource_manager_disabled_default_action + enum = local.securitycenters_with_azure_defender_for_resource_manager_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.securitycenters_with_azure_defender_for_resource_manager_disabled_enabled_actions + enum = local.securitycenters_with_azure_defender_for_resource_manager_disabled_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.securitycenters_with_azure_defender_for_resource_manager_disabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_securitycenters_with_azure_defender_for_resource_manager_disabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_securitycenters_with_azure_defender_for_resource_manager_disabled" { + title = "Correct Security Centers with Azure Defender disabled for Resource Manager" + description = "Enable Azure Defender for Resource Manager in Security Centers with Azure Defender disabled for Resource Manager." + tags = merge(local.securitycenter_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + name = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.securitycenters_with_azure_defender_for_resource_manager_disabled_default_action + enum = local.securitycenters_with_azure_defender_for_resource_manager_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.securitycenters_with_azure_defender_for_resource_manager_disabled_enabled_actions + enum = local.securitycenters_with_azure_defender_for_resource_manager_disabled_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} Security Center(s) with Azure Defender disabled for Resource Manager." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.id => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_securitycenter_with_azure_defender_for_resource_manager_disabled + args = { + title = each.value.title + name = each.value.name + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_securitycenter_with_azure_defender_for_resource_manager_disabled" { + title = "Correct Security Center with Azure Defender disabled for Resource Manager" + description = "Enable Azure Defender for Resource Manager in Security Center with Azure Defender disabled for Resource Manager." + tags = merge(local.securitycenter_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "name" { + type = string + description = "The name of the security center subscription pricing." + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.securitycenters_with_azure_defender_for_resource_manager_disabled_default_action + enum = local.securitycenters_with_azure_defender_for_resource_manager_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.securitycenters_with_azure_defender_for_resource_manager_disabled_enabled_actions + enum = local.securitycenters_with_azure_defender_for_resource_manager_disabled_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected Security Center ${param.title} with Azure Defender disabled for Resource Manager." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped Security Center ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "enable_resource_manager_azure_defender" = { + label = "Enable Resource Manager Azure Defender" + value = "enable_resource_manager_azure_defender" + style = local.style_alert + pipeline_ref = azure.pipeline.create_security_pricing + pipeline_args = { + resource_type = "Arm" + subscription_id = param.subscription_id + conn = param.conn + tier = "Standard" + } + success_msg = "Enabled Azure Defender for Resource Manager in Security Center ${param.title}." + error_msg = "Error enabling Azure Defender for Resource Manager in Security Center ${param.title}." + } + } + } + } +} diff --git a/pipelines/securitycenter/securitycenters_with_azure_defender_for_server_disabled.fp b/pipelines/securitycenter/securitycenters_with_azure_defender_for_server_disabled.fp new file mode 100644 index 0000000..5536d1e --- /dev/null +++ b/pipelines/securitycenter/securitycenters_with_azure_defender_for_server_disabled.fp @@ -0,0 +1,314 @@ +locals { + securitycenters_with_azure_defender_for_server_disabled_query = <<-EOQ + select + concat(sc.id, ' [', '/', sc.subscription_id, ']') as title, + sc.id as id, + sc.name, + sc.subscription_id, + sc._ctx ->> 'connection_name' as conn + from + azure_security_center_subscription_pricing as sc, + azure_subscription as sub + where + sc.pricing_tier != 'Standard' + and sc.name = 'VirtualMachines' + and sub.subscription_id = sc.subscription_id; + EOQ + + securitycenters_with_azure_defender_for_server_disabled_enabled_actions_enum = ["skip", "enable_server_azure_defender"] + securitycenters_with_azure_defender_for_server_disabled_default_action_enum = ["notify", "skip", "enable_server_azure_defender"] +} + +variable "securitycenters_with_azure_defender_for_server_disabled_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +variable "securitycenters_with_azure_defender_for_server_disabled_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +variable "securitycenters_with_azure_defender_for_server_disabled_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +variable "securitycenters_with_azure_defender_for_server_disabled_enabled_actions" { + type = list(string) + description = "The list of enabled actions to provide to approvers for selection." + default = ["skip", "enable_server_azure_defender"] + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +trigger "query" "detect_and_correct_securitycenters_with_azure_defender_for_server_disabled" { + title = "Detect & correct Security Centers with Azure Defender disabled for Server" + description = "Detect Security Centers with Azure Defender disabled for Server and then enable Azure Defender for Server." + tags = local.securitycenter_common_tags + + enabled = var.securitycenters_with_azure_defender_for_server_disabled_trigger_enabled + schedule = var.securitycenters_with_azure_defender_for_server_disabled_trigger_schedule + database = var.database + sql = local.securitycenters_with_azure_defender_for_server_disabled_query + + capture "insert" { + pipeline = pipeline.correct_securitycenters_with_azure_defender_for_server_disabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_securitycenters_with_azure_defender_for_server_disabled" { + title = "Detect & correct Security Centers with Azure Defender disabled for Server" + description = "Detect Security Centers with Azure Defender disabled for Server and then enable Azure Defender for Server." + tags = local.securitycenter_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.securitycenters_with_azure_defender_for_server_disabled_default_action + enum = local.securitycenters_with_azure_defender_for_server_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.securitycenters_with_azure_defender_for_server_disabled_enabled_actions + enum = local.securitycenters_with_azure_defender_for_server_disabled_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.securitycenters_with_azure_defender_for_server_disabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_securitycenters_with_azure_defender_for_server_disabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_securitycenters_with_azure_defender_for_server_disabled" { + title = "Correct Security Centers with Azure Defender disabled for Server" + description = "Enable Azure Defender for Server in Security Centers with Azure Defender disabled for Server." + tags = merge(local.securitycenter_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + name = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.securitycenters_with_azure_defender_for_server_disabled_default_action + enum = local.securitycenters_with_azure_defender_for_server_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.securitycenters_with_azure_defender_for_server_disabled_enabled_actions + enum = local.securitycenters_with_azure_defender_for_server_disabled_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} Security Center(s) with Azure Defender disabled for Server." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.id => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_securitycenter_with_azure_defender_for_server_disabled + args = { + title = each.value.title + name = each.value.name + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + + +pipeline "correct_one_securitycenter_with_azure_defender_for_server_disabled" { + title = "Correct Security Center with Azure Defender disabled for Server" + description = "Enable Azure Defender for Server in Security Center with Azure Defender disabled for Server." + tags = merge(local.securitycenter_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "name" { + type = string + description = "The name of the security center subscription pricing." + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.securitycenters_with_azure_defender_for_server_disabled_default_action + enum = local.securitycenters_with_azure_defender_for_server_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.securitycenters_with_azure_defender_for_server_disabled_enabled_actions + enum = local.securitycenters_with_azure_defender_for_server_disabled_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected Security Center ${param.title} with Azure Defender disabled for Server." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped Security Center ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "enable_server_azure_defender" = { + label = "Enable Server Azure Defender" + value = "enable_server_azure_defender" + style = local.style_alert + pipeline_ref = azure.pipeline.create_security_pricing + pipeline_args = { + resource_type = "VirtualMachines" + subscription_id = param.subscription_id + conn = param.conn + tier = "Standard" + } + success_msg = "Enabled Azure Defender for Server in Security Center ${param.title}." + error_msg = "Error enabling Azure Defender for Server in Security Center ${param.title}." + } + } + } + } +} diff --git a/pipelines/securitycenter/securitycenters_with_azure_defender_for_sql_db_disabled.fp b/pipelines/securitycenter/securitycenters_with_azure_defender_for_sql_db_disabled.fp new file mode 100644 index 0000000..d245be8 --- /dev/null +++ b/pipelines/securitycenter/securitycenters_with_azure_defender_for_sql_db_disabled.fp @@ -0,0 +1,315 @@ +locals { + securitycenters_with_azure_defender_for_sql_db_disabled_query = <<-EOQ + select + concat(sc.id, ' [', '/', sc.subscription_id, ']') as title, + sc.id as id, + sc.name, + sc.subscription_id, + sc._ctx ->> 'connection_name' as conn + from + azure_security_center_subscription_pricing as sc, + azure_subscription as sub + where + sc.pricing_tier != 'Standard' + and sc.name = 'SqlServers' + and sub.subscription_id = sc.subscription_id; + EOQ + + securitycenters_with_azure_defender_for_sql_db_disabled_enabled_actions_enum = ["skip", "enable_sqldb_azure_defender"] + securitycenters_with_azure_defender_for_sql_db_disabled_default_action_enum = ["notify", "skip", "enable_sqldb_azure_defender"] +} + +variable "securitycenters_with_azure_defender_for_sql_db_disabled_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +variable "securitycenters_with_azure_defender_for_sql_db_disabled_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +variable "securitycenters_with_azure_defender_for_sql_db_disabled_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +variable "securitycenters_with_azure_defender_for_sql_db_disabled_enabled_actions" { + type = list(string) + description = "The list of enabled actions to provide to approvers for selection." + default = ["skip", "enable_sqldb_azure_defender"] + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +trigger "query" "detect_and_correct_securitycenters_with_azure_defender_for_sql_db_disabled" { + title = "Detect & correct Security Centers with Azure Defender disabled for SQL Database" + description = "Detect Security Centers with Azure Defender disabled for SQL Database and then enable Azure Defender for SQL Database." + tags = local.securitycenter_common_tags + + enabled = var.securitycenters_with_azure_defender_for_sql_db_disabled_trigger_enabled + schedule = var.securitycenters_with_azure_defender_for_sql_db_disabled_trigger_schedule + database = var.database + sql = local.securitycenters_with_azure_defender_for_sql_db_disabled_query + + capture "insert" { + pipeline = pipeline.correct_securitycenters_with_azure_defender_for_sql_db_disabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_securitycenters_with_azure_defender_for_sql_db_disabled" { + title = "Detect & correct Security Centers with Azure Defender disabled for SQL Database" + description = "Detect Security Centers with Azure Defender disabled for SQL Database and then enable Azure Defender for SQL Database." + tags = local.securitycenter_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.securitycenters_with_azure_defender_for_sql_db_disabled_default_action + enum = local.securitycenters_with_azure_defender_for_sql_db_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.securitycenters_with_azure_defender_for_sql_db_disabled_enabled_actions + enum = local.securitycenters_with_azure_defender_for_sql_db_disabled_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.securitycenters_with_azure_defender_for_sql_db_disabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_securitycenters_with_azure_defender_for_sql_db_disabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_securitycenters_with_azure_defender_for_sql_db_disabled" { + title = "Correct Security Centers with Azure Defender disabled for SQL Database" + description = "Enable Azure Defender for SQL Database in Security Centers with Azure Defender disabled for SQL Database." + tags = merge(local.securitycenter_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + name = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.securitycenters_with_azure_defender_for_sql_db_disabled_default_action + enum = local.securitycenters_with_azure_defender_for_sql_db_disabled_default_action_enum + } + + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.securitycenters_with_azure_defender_for_sql_db_disabled_enabled_actions + enum = local.securitycenters_with_azure_defender_for_sql_db_disabled_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} Security Center(s) with Azure Defender disabled for SQL Database." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.id => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_securitycenter_with_azure_defender_for_sql_db_disabled + args = { + title = each.value.title + name = each.value.name + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_securitycenter_with_azure_defender_for_sql_db_disabled" { + title = "Correct Security Center with Azure Defender disabled for SQL Database" + description = "Enable Azure Defender for SQL Database in Security Center with Azure Defender disabled for SQL Database." + tags = merge(local.securitycenter_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "name" { + type = string + description = "The name of the security center subscription pricing." + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.securitycenters_with_azure_defender_for_sql_db_disabled_default_action + enum = local.securitycenters_with_azure_defender_for_sql_db_disabled_default_action_enum + } + + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.securitycenters_with_azure_defender_for_sql_db_disabled_enabled_actions + enum = local.securitycenters_with_azure_defender_for_sql_db_disabled_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected Security Center ${param.title} with Azure Defender disabled for SQL Database." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped Security Center ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "enable_sqldb_azure_defender" = { + label = "Enable SQL Databases Azure Defender" + value = "enable_sqldb_azure_defender" + style = local.style_alert + pipeline_ref = azure.pipeline.create_security_pricing + pipeline_args = { + resource_type = "SqlServers" + subscription_id = param.subscription_id + conn = param.conn + tier = "Standard" + } + success_msg = "Enabled Azure Defender for SQL Database in Security Center ${param.title}." + error_msg = "Error enabling Azure Defender for SQL Database in Security Center ${param.title}." + } + } + } + } +} diff --git a/pipelines/securitycenter/securitycenters_with_azure_defender_for_sql_server_vm_disabled.fp b/pipelines/securitycenter/securitycenters_with_azure_defender_for_sql_server_vm_disabled.fp new file mode 100644 index 0000000..a9ee3c9 --- /dev/null +++ b/pipelines/securitycenter/securitycenters_with_azure_defender_for_sql_server_vm_disabled.fp @@ -0,0 +1,315 @@ +locals { + securitycenters_with_azure_defender_for_sql_server_vm_disabled_query = <<-EOQ + select + concat(sc.id, ' [', '/', sc.subscription_id, ']') as title, + sc.id as id, + sc.name, + sc.subscription_id, + sc._ctx ->> 'connection_name' as conn + from + azure_security_center_subscription_pricing as sc, + azure_subscription as sub + where + sc.pricing_tier != 'Standard' + and sc.name = 'SqlServerVirtualMachines' + and sub.subscription_id = sc.subscription_id; + EOQ + + securitycenters_with_azure_defender_for_sql_server_vm_disabled_enabled_actions_enum = ["skip", "enable_sql_server_vm_azure_defender"] + securitycenters_with_azure_defender_for_sql_server_vm_disabled_default_action_enum = ["notify", "skip", "enable_sql_server_vm_azure_defender"] +} + +variable "securitycenters_with_azure_defender_for_sql_server_vm_disabled_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +variable "securitycenters_with_azure_defender_for_sql_server_vm_disabled_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +variable "securitycenters_with_azure_defender_for_sql_server_vm_disabled_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +variable "securitycenters_with_azure_defender_for_sql_server_vm_disabled_enabled_actions" { + type = list(string) + description = "The list of enabled actions to provide to approvers for selection." + default = ["skip", "enable_sql_server_vm_azure_defender"] + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +trigger "query" "detect_and_correct_securitycenters_with_azure_defender_for_sql_server_vm_disabled" { + title = "Detect & correct Security Centers with Azure Defender disabled for SQL Server Virtual Machine" + description = "Detect Security Centers with Azure Defender disabled for SQL Server Virtual Machine and then enable Azure Defender for SQL Server Virtual Machine." + tags = local.securitycenter_common_tags + + enabled = var.securitycenters_with_azure_defender_for_sql_server_vm_disabled_trigger_enabled + schedule = var.securitycenters_with_azure_defender_for_sql_server_vm_disabled_trigger_schedule + database = var.database + sql = local.securitycenters_with_azure_defender_for_sql_server_vm_disabled_query + + capture "insert" { + pipeline = pipeline.correct_securitycenters_with_azure_defender_for_sql_server_vm_disabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_securitycenters_with_azure_defender_for_sql_server_vm_disabled" { + title = "Detect & correct Security Centers with Azure Defender disabled for SQL Server Virtual Machine" + description = "Detect Security Centers with Azure Defender disabled for SQL Server Virtual Machine and then enable Azure Defender for SQL Server Virtual Machine." + tags = local.securitycenter_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.securitycenters_with_azure_defender_for_sql_server_vm_disabled_default_action + enum = local.securitycenters_with_azure_defender_for_sql_server_vm_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.securitycenters_with_azure_defender_for_sql_server_vm_disabled_enabled_actions + enum = local.securitycenters_with_azure_defender_for_sql_server_vm_disabled_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.securitycenters_with_azure_defender_for_sql_server_vm_disabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_securitycenters_with_azure_defender_for_sql_server_vm_disabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_securitycenters_with_azure_defender_for_sql_server_vm_disabled" { + title = "Correct Security Centers with Azure Defender disabled for SQL Server Virtual Machine" + description = "Enable Azure Defender for SQL Server Virtual Machine in Security Centers with Azure Defender disabled for SQL Server Virtual Machine." + tags = merge(local.securitycenter_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + name = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.securitycenters_with_azure_defender_for_sql_server_vm_disabled_default_action + enum = local.securitycenters_with_azure_defender_for_sql_server_vm_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.securitycenters_with_azure_defender_for_sql_server_vm_disabled_enabled_actions + enum = local.securitycenters_with_azure_defender_for_sql_server_vm_disabled_enabled_actions_enum + } + + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} Security Center(s) with Azure Defender disabled for SQL Server Virtual Machine." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.id => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_securitycenter_with_azure_defender_for_sql_server_vm_disabled + args = { + title = each.value.title + name = each.value.name + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_securitycenter_with_azure_defender_for_sql_server_vm_disabled" { + title = "Correct Security Center with Azure Defender disabled for SQL Server Virtual Machine" + description = "Enable Azure Defender for SQL Server Virtual Machine in Security Center with Azure Defender disabled for SQL Server Virtual Machine." + tags = merge(local.securitycenter_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "name" { + type = string + description = "The name of the security center subscription pricing." + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.securitycenters_with_azure_defender_for_sql_server_vm_disabled_default_action + enum = local.securitycenters_with_azure_defender_for_sql_server_vm_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.securitycenters_with_azure_defender_for_sql_server_vm_disabled_enabled_actions + enum = local.securitycenters_with_azure_defender_for_sql_server_vm_disabled_enabled_actions_enum + } + + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected Security Center ${param.title} with Azure Defender disabled for SQL Server Virtual Machine." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped Security Center ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "enable_sql_server_vm_azure_defender" = { + label = "Enable SQL servers on machines Azure Defender" + value = "enable_sql_server_vm_azure_defender" + style = local.style_alert + pipeline_ref = azure.pipeline.create_security_pricing + pipeline_args = { + resource_type = "SqlServerVirtualMachines" + subscription_id = param.subscription_id + conn = param.conn + tier = "Standard" + } + success_msg = "Enabled Azure Defender for SQL Server Virtual Machine in Security Center ${param.title}." + error_msg = "Error enabling Azure Defender for SQL Server Virtual Machine in Security Center ${param.title}." + } + } + } + } +} diff --git a/pipelines/securitycenter/securitycenters_with_azure_defender_for_storage_disabled.fp b/pipelines/securitycenter/securitycenters_with_azure_defender_for_storage_disabled.fp new file mode 100644 index 0000000..7507585 --- /dev/null +++ b/pipelines/securitycenter/securitycenters_with_azure_defender_for_storage_disabled.fp @@ -0,0 +1,315 @@ +locals { + securitycenters_with_azure_defender_for_storage_disabled_query = <<-EOQ + select + concat(sc.id, ' [', '/', sc.subscription_id, ']') as title, + sc.id as id, + sc.name, + sc.subscription_id, + sc._ctx ->> 'connection_name' as conn + from + azure_security_center_subscription_pricing as sc, + azure_subscription as sub + where + sc.pricing_tier != 'Standard' + and sc.name = 'Storage' + and sub.subscription_id = sc.subscription_id; + EOQ + + securitycenters_with_azure_defender_for_storage_disabled_enabled_actions_enum = ["skip", "enable_storage_azure_defender"] + securitycenters_with_azure_defender_for_storage_disabled_default_action_enum = ["notify", "skip", "enable_storage_azure_defender"] +} + +variable "securitycenters_with_azure_defender_for_storage_disabled_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +variable "securitycenters_with_azure_defender_for_storage_disabled_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +variable "securitycenters_with_azure_defender_for_storage_disabled_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +variable "securitycenters_with_azure_defender_for_storage_disabled_enabled_actions" { + type = list(string) + description = "The list of enabled actions to provide to approvers for selection." + default = ["skip", "enable_storage_azure_defender"] + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +trigger "query" "detect_and_correct_securitycenters_with_azure_defender_for_storage_disabled" { + title = "Detect & correct Security Centers with Azure Defender disabled for Storage" + description = "Detect Security Centers with Azure Defender disabled for Storage and then enable Azure Defender for Storage." + tags = local.securitycenter_common_tags + + enabled = var.securitycenters_with_azure_defender_for_storage_disabled_trigger_enabled + schedule = var.securitycenters_with_azure_defender_for_storage_disabled_trigger_schedule + database = var.database + sql = local.securitycenters_with_azure_defender_for_storage_disabled_query + + capture "insert" { + pipeline = pipeline.correct_securitycenters_with_azure_defender_for_storage_disabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_securitycenters_with_azure_defender_for_storage_disabled" { + title = "Detect & correct Security Centers with Azure Defender disabled for Storage" + description = "Detect Security Centers with Azure Defender disabled for Storage and then enable Azure Defender for Storage." + tags = local.securitycenter_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.securitycenters_with_azure_defender_for_storage_disabled_default_action + enum = local.securitycenters_with_azure_defender_for_storage_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.securitycenters_with_azure_defender_for_storage_disabled_enabled_actions + enum = local.securitycenters_with_azure_defender_for_storage_disabled_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.securitycenters_with_azure_defender_for_storage_disabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_securitycenters_with_azure_defender_for_storage_disabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_securitycenters_with_azure_defender_for_storage_disabled" { + title = "Correct Security Centers with Azure Defender disabled for Storage" + description = "Enable Azure Defender for Storage in Security Centers with Azure Defender disabled for Storage." + tags = merge(local.securitycenter_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + name = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.securitycenters_with_azure_defender_for_storage_disabled_default_action + enum = local.securitycenters_with_azure_defender_for_storage_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.securitycenters_with_azure_defender_for_storage_disabled_enabled_actions + enum = local.securitycenters_with_azure_defender_for_storage_disabled_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} Security Center(s) with Azure Defender disabled for Storage." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.id => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_securitycenter_with_azure_defender_for_storage_disabled + args = { + title = each.value.title + name = each.value.name + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_securitycenter_with_azure_defender_for_storage_disabled" { + title = "Correct Security Center with Azure Defender disabled for Storage" + description = "Enable Azure Defender for Storage in Security Center with Azure Defender disabled for Storage." + tags = merge(local.securitycenter_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "name" { + type = string + description = "The name of the security center subscription pricing." + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.securitycenters_with_azure_defender_for_storage_disabled_default_action + enum = local.securitycenters_with_azure_defender_for_storage_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.securitycenters_with_azure_defender_for_storage_disabled_enabled_actions + enum = local.securitycenters_with_azure_defender_for_storage_disabled_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected Security Center ${param.title} with Azure Defender disabled for Storage." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped Security Center ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "enable_storage_azure_defender" = { + label = "Enable Storage Azure Defender" + value = "enable_storage_azure_defender" + style = local.style_alert + pipeline_ref = azure.pipeline.create_security_pricing + pipeline_args = { + resource_type = "Storage" + subscription_id = param.subscription_id + conn = param.conn + tier = "Standard" + } + success_msg = "Enabled Azure Defender for Storage in Security Center ${param.title}." + error_msg = "Error enabling Azure Defender for Storage in Security Center ${param.title}." + } + } + } + } +} + + diff --git a/pipelines/securitycenter/securitycenters_with_security_alerts_to_owner_disabled.fp b/pipelines/securitycenter/securitycenters_with_security_alerts_to_owner_disabled.fp new file mode 100644 index 0000000..8a76cfb --- /dev/null +++ b/pipelines/securitycenter/securitycenters_with_security_alerts_to_owner_disabled.fp @@ -0,0 +1,139 @@ +locals { + securitycenters_with_security_alerts_to_owner_disabled_query = <<-EOQ + with contact_info as ( + select + count(*) filter (where alerts_to_admins = 'On') as admin_alert_count, + subscription_id + from + azure_security_center_contact + group by + subscription_id + limit 1 + ) + select + sub.subscription_id as title, + sub._ctx ->> 'connection_name' as conn + from + azure_subscription sub + left join contact_info ci on sub.subscription_id = ci.subscription_id + where + not admin_alert_count > 0 or admin_alert_count is null; + EOQ +} + +variable "securitycenters_with_security_alerts_to_owner_disabled_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +variable "securitycenters_with_security_alerts_to_owner_disabled_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +trigger "query" "detect_and_correct_securitycenters_with_security_alerts_to_owner_disabled" { + title = "Detect & correct Security Centers with security alerts to owner disabled" + description = "Detect Security Centers with security alerts to owner disabled." + tags = local.securitycenter_common_tags + + enabled = var.securitycenters_with_security_alerts_to_owner_disabled_trigger_enabled + schedule = var.securitycenters_with_security_alerts_to_owner_disabled_trigger_schedule + database = var.database + sql = local.securitycenters_with_security_alerts_to_owner_disabled_query + + capture "insert" { + pipeline = pipeline.correct_securitycenters_with_security_alerts_to_owner_disabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_securitycenters_with_security_alerts_to_owner_disabled" { + title = "Detect & correct Security Centers with security alerts to owner disabled" + description = "Detect Security Centers with security alerts to owner disabled." + tags = local.securitycenter_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.securitycenters_with_security_alerts_to_owner_disabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_securitycenters_with_security_alerts_to_owner_disabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_securitycenters_with_security_alerts_to_owner_disabled" { + title = "Correct Security Centers with security alerts to owner disabled" + description = "Send notifications for Security Centers with security alerts to owner disabled." + tags = merge(local.securitycenter_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} Security Centers with security alerts to owner disabled." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected Security Center ${each.value.title} with security alerts to owner disabled" + } +} diff --git a/pipelines/securitycenter/securitycenters_without_additional_email_configured.fp b/pipelines/securitycenter/securitycenters_without_additional_email_configured.fp new file mode 100644 index 0000000..28f61f3 --- /dev/null +++ b/pipelines/securitycenter/securitycenters_without_additional_email_configured.fp @@ -0,0 +1,141 @@ +locals { + securitycenters_without_additional_email_configured_query = <<-EOQ + with contact_info as ( + select + jsonb_agg(email) filter (where name = 'default' and email != '') as default_email, + count(*) filter (where name != 'default') as non_default_count, + count(*) filter (where name = 'default') as default_count, + subscription_id + from + azure_security_center_contact + group by + subscription_id + limit 1 + ) + select + sub.subscription_id as title, + sub._ctx ->> 'connection_name' as conn + from + azure_subscription sub + left join contact_info ci on sub.subscription_id = ci.subscription_id + where + not non_default_count > 0 or non_default_count is null; + EOQ +} + +variable "securitycenters_without_additional_email_configured_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +variable "securitycenters_without_additional_email_configured_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +trigger "query" "detect_and_correct_securitycenters_without_additional_email_configured" { + title = "Detect & correct Security Centers without additional email configured" + description = "Detect Security Centers without additional email configured." + tags = local.securitycenter_common_tags + + enabled = var.securitycenters_without_additional_email_configured_trigger_enabled + schedule = var.securitycenters_without_additional_email_configured_trigger_schedule + database = var.database + sql = local.securitycenters_without_additional_email_configured_query + + capture "insert" { + pipeline = pipeline.correct_securitycenters_without_additional_email_configured + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_securitycenters_without_additional_email_configured" { + title = "Detect & correct Security Centers without additional email configured" + description = "Detect Security Centers without additional email configured." + tags = local.securitycenter_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.securitycenters_without_additional_email_configured_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_securitycenters_without_additional_email_configured + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_securitycenters_without_additional_email_configured" { + title = "Correct Security Centers without additional email configured" + description = "Send notifications for Security Centers without additional email configured." + tags = merge(local.securitycenter_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} Subscription(s) Security Center without additional email configured." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected Subscription ${each.value.title} without Security Center additional email configured." + } +} diff --git a/pipelines/securitycenter/securitycenters_without_notify_alerts_configured.fp b/pipelines/securitycenter/securitycenters_without_notify_alerts_configured.fp new file mode 100644 index 0000000..d7d9eb3 --- /dev/null +++ b/pipelines/securitycenter/securitycenters_without_notify_alerts_configured.fp @@ -0,0 +1,139 @@ +locals { + securitycenters_without_notify_alerts_configured_query = <<-EOQ + with contact_info as ( + select + count(*) filter (where alert_notifications = 'On') as notification_alert_count, + subscription_id + from + azure_security_center_contact + group by + subscription_id + limit 1 + ) + select + sub.subscription_id as title, + sub._ctx ->> 'connection_name' as conn + from + azure_subscription sub + left join contact_info ci on sub.subscription_id = ci.subscription_id + where + not notification_alert_count > 0 or notification_alert_count is null; + EOQ +} + +variable "securitycenters_without_notify_alerts_configured_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +variable "securitycenters_without_notify_alerts_configured_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/SecurityCenter" + } +} + +trigger "query" "detect_and_correct_securitycenters_without_notify_alerts_configured" { + title = "Detect & correct Security Centers without notify alerts configured" + description = "Detect Security Centers without notify alerts configured." + tags = local.securitycenter_common_tags + + enabled = var.securitycenters_without_notify_alerts_configured_trigger_enabled + schedule = var.securitycenters_without_notify_alerts_configured_trigger_schedule + database = var.database + sql = local.securitycenters_without_notify_alerts_configured_query + + capture "insert" { + pipeline = pipeline.correct_securitycenters_without_notify_alerts_configured + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_securitycenters_without_notify_alerts_configured" { + title = "Detect & correct Security Centers without notify alerts configured" + description = "Detect Security Centers without notify alerts configured." + tags = local.securitycenter_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.securitycenters_without_notify_alerts_configured_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_securitycenters_without_notify_alerts_configured + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_securitycenters_without_notify_alerts_configured" { + title = "Correct Security Centers without notify alerts configured" + description = "Send notifications for Security Centers without notify alerts configured." + tags = merge(local.securitycenter_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} Subscription(s) Security Center without notify alerts configured." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected Subscription ${each.value.title} without Security Center without notify alerts configured." + } +} diff --git a/pipelines/securitycenter/securitycentre.fp b/pipelines/securitycenter/securitycentre.fp new file mode 100644 index 0000000..54973ae --- /dev/null +++ b/pipelines/securitycenter/securitycentre.fp @@ -0,0 +1,5 @@ +locals { + securitycenter_common_tags = merge(local.azure_compliance_common_tags, { + service = "Azure/SecurityCenter" + }) +} diff --git a/pipelines/sql/sql.fp b/pipelines/sql/sql.fp new file mode 100644 index 0000000..1defbab --- /dev/null +++ b/pipelines/sql/sql.fp @@ -0,0 +1,5 @@ +locals { + sql_common_tags = merge(local.azure_compliance_common_tags, { + service = "Azure/SQL" + }) +} diff --git a/pipelines/sql/sql_databases_with_public_access_enabled.fp b/pipelines/sql/sql_databases_with_public_access_enabled.fp new file mode 100644 index 0000000..28c9212 --- /dev/null +++ b/pipelines/sql/sql_databases_with_public_access_enabled.fp @@ -0,0 +1,334 @@ +locals { + sql_databases_with_public_access_enabled_query = <<-EOQ + select + distinct concat(s.id, ' [', s.subscription_id, '/', s.resource_group, '/firewallrule/', f ->> 'name',']') as title, + s.id as id, + s.name, + f ->> 'name' as firewall_rule_name, + s.resource_group, + s.subscription_id, + s._ctx ->> 'connection_name' as conn + from + azure_sql_server s, + jsonb_array_elements(firewall_rules) as f, + azure_subscription sub + where + sub.subscription_id = s.subscription_id + and ( + (f -> 'properties' ->> 'endIpAddress' = '0.0.0.0' and f -> 'properties' ->> 'startIpAddress' = '0.0.0.0') + or + ( f -> 'properties' ->> 'endIpAddress' = '255.255.255.255' and f -> 'properties' ->> 'startIpAddress' = '0.0.0.0') + ); + EOQ + + sql_databases_with_public_access_enabled_enabled_actions_enum = ["skip", "revoke_firewall_rule"] + sql_databases_with_public_access_enabled_default_action_enum = ["notify", "skip", "revoke_firewall_rule"] +} + +variable "sql_databases_with_public_access_enabled_trigger_enabled" { + type = bool + default = false + description = "If true, the trigger is enabled." + + tags = { + folder = "Advanced/SQL" + } +} + +variable "sql_databases_with_public_access_enabled_trigger_schedule" { + type = string + default = "15m" + description = "If the trigger is enabled, run it on this schedule." + + tags = { + folder = "Advanced/SQL" + } +} + +variable "sql_databases_with_public_access_enabled_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/SQL" + } +} + +variable "sql_databases_with_public_access_enabled_enabled_actions" { + type = list(string) + description = "The list of enabled actions to provide to approvers for selection." + default = ["skip", "revoke_firewall_rule"] + + tags = { + folder = "Advanced/SQL" + } +} + +trigger "query" "detect_and_correct_sql_databases_with_public_access_enabled" { + title = "Detect & correct SQL Databases with public access enabled" + description = "Detect SQL Databases firewall rules allowing public access and then revoke the firewall rules." + tags = local.sql_common_tags + + enabled = var.sql_databases_with_public_access_enabled_trigger_enabled + schedule = var.sql_databases_with_public_access_enabled_trigger_schedule + database = var.database + sql = local.sql_databases_with_public_access_enabled_query + + capture "insert" { + pipeline = pipeline.correct_sql_databases_with_public_access_enabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_sql_databases_with_public_access_enabled" { + title = "Detect & correct SQL Databases with public access enabled" + description = "Detect SQL Databases firewall rules allowing public access and then revoke the firewall rules." + tags = merge(local.sql_common_tags, { recommended = "true" }) + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.sql_databases_with_public_access_enabled_default_action + enum = local.sql_databases_with_public_access_enabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.sql_databases_with_public_access_enabled_enabled_actions + enum = local.sql_databases_with_public_access_enabled_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.sql_databases_with_public_access_enabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_sql_databases_with_public_access_enabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_sql_databases_with_public_access_enabled" { + title = "Correct SQL Databases with public access enabled" + description = "Revoke firewall rule for SQL Databases allowing public access." + tags = merge(local.sql_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + name = string + resource_group = string + firewall_rule_name = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.sql_databases_with_public_access_enabled_default_action + enum = local.sql_databases_with_public_access_enabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.sql_databases_with_public_access_enabled_enabled_actions + enum = local.sql_databases_with_public_access_enabled_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} SQL Database(s) allowing public access." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.title => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_sql_database_with_public_access_enabled + args = { + title = each.value.title + name = each.value.name + resource_group = each.value.resource_group + firewall_rule_name = each.value.firewall_rule_name + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_sql_database_with_public_access_enabled" { + title = "Correct SQL Database with public access enabled" + description = "Revoke firewall rule for a SQL Database allowing public access." + tags = merge(local.sql_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "name" { + type = string + description = "The name of the SQL Database." + } + + param "resource_group" { + type = string + description = local.description_resource_group + } + + param "firewall_rule_name" { + type = string + description = "The firewall rule name." + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.sql_databases_with_public_access_enabled_default_action + enum = local.sql_databases_with_public_access_enabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.sql_databases_with_public_access_enabled_enabled_actions + enum = local.sql_databases_with_public_access_enabled_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected SQL Database ${param.title} allowing public access." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped SQL Database ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "revoke_firewall_rule" = { + label = "Revoke firewall rule" + value = "revoke_firewall_rule" + style = local.style_alert + pipeline_ref = azure.pipeline.delete_sql_server_firewall_rule + pipeline_args = { + resource_group = param.resource_group + subscription_id = param.subscription_id + server_name = param.name + conn = param.conn + firewall_rule_name = param.firewall_rule_name + } + success_msg = "Revoked firewall rule allowing public access for SQL Database ${param.title}." + error_msg = "Error revoking firewall rule allowing public access for SQL Database ${param.title}." + } + } + } + } +} diff --git a/pipelines/sql/sql_databases_with_transparent_data_encryption_disabled.fp b/pipelines/sql/sql_databases_with_transparent_data_encryption_disabled.fp new file mode 100644 index 0000000..ef11836 --- /dev/null +++ b/pipelines/sql/sql_databases_with_transparent_data_encryption_disabled.fp @@ -0,0 +1,329 @@ +locals { + sql_databases_with_transparent_data_encryption_disabled_query = <<-EOQ + select + concat(s.id, ' [', s.subscription_id, '/', s.resource_group, ']') as title, + s.id as id, + s.server_name as server_name, + s.name as name, + s.resource_group, + s.subscription_id, + s._ctx ->> 'connection_name' as conn + from + azure_sql_database s + where + name <> 'master' + and ( transparent_data_encryption ->> 'status' <> 'Enabled' or transparent_data_encryption ->> 'state' <> 'Enabled' or transparent_data_encryption is null); + EOQ + + sql_databases_with_transparent_data_encryption_disabled_enabled_actions_enum = ["skip", "enable_sql_db_tde"] + sql_databases_with_transparent_data_encryption_disabled_default_action_enum = ["notify", "skip", "enable_sql_db_tde"] +} + +variable "sql_databases_with_transparent_data_encryption_disabled_trigger_enabled" { + type = bool + default = false + description = "If true, the trigger is enabled." + + tags = { + folder = "Advanced/SQL" + } +} + +variable "sql_databases_with_transparent_data_encryption_disabled_trigger_schedule" { + type = string + default = "15m" + description = "If the trigger is enabled, run it on this schedule." + + tags = { + folder = "Advanced/SQL" + } +} + +variable "sql_databases_with_transparent_data_encryption_disabled_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/SQL" + } +} + +variable "sql_databases_with_transparent_data_encryption_disabled_enabled_actions" { + type = list(string) + description = "The list of enabled actions to provide to approvers for selection." + default = ["skip", "enable_sql_db_tde"] + + tags = { + folder = "Advanced/SQL" + } +} + +trigger "query" "detect_and_correct_sql_databases_with_transparent_data_encryption_disabled" { + title = "Detect & correct SQL Databases with transparent data encryption disabled" + description = "Detect SQL Databases with transparent data encryption disabled and enable transparent data encryption." + tags = local.sql_common_tags + + enabled = var.sql_databases_with_transparent_data_encryption_disabled_trigger_enabled + schedule = var.sql_databases_with_transparent_data_encryption_disabled_trigger_schedule + database = var.database + sql = local.sql_databases_with_transparent_data_encryption_disabled_query + + capture "insert" { + pipeline = pipeline.correct_sql_databases_with_transparent_data_encryption_disabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_sql_databases_with_transparent_data_encryption_disabled" { + title = "Detect & correct SQL Databases with transparent data encryption disabled" + description = "Detect SQL Databases with transparent data encryption disabled and enable transparent data encryption." + tags = merge(local.sql_common_tags, { recommended = "true" }) + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.sql_databases_with_transparent_data_encryption_disabled_default_action + enum = local.sql_databases_with_transparent_data_encryption_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.sql_databases_with_transparent_data_encryption_disabled_enabled_actions + enum = local.sql_databases_with_transparent_data_encryption_disabled_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.sql_databases_with_transparent_data_encryption_disabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_sql_databases_with_transparent_data_encryption_disabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_sql_databases_with_transparent_data_encryption_disabled" { + title = "Correct SQL Databases with transparent data encryption disabled" + description = "Enable transparent data encryption for SQL Databases with transparent data encryption disabled." + tags = merge(local.sql_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + server_name = string + name = string + resource_group = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.sql_databases_with_transparent_data_encryption_disabled_default_action + enum = local.sql_databases_with_transparent_data_encryption_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.sql_databases_with_transparent_data_encryption_disabled_enabled_actions + enum = local.sql_databases_with_transparent_data_encryption_disabled_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} SQL Database(s) with transparent data encryption disabled." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.title => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_sql_database_with_transparent_data_encryption_disabled + args = { + title = each.value.title + server_name = each.value.server_name + name = each.value.name + resource_group = each.value.resource_group + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_sql_database_with_transparent_data_encryption_disabled" { + title = "Correct SQL Database with transparent data encryption disabled" + description = "Enable transparent data encryption for a SQL Database with transparent data encryption disabled." + tags = merge(local.sql_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "server_name" { + type = string + description = "The name of the SQL Server." + } + + param "name" { + type = string + description = "The name of the SQL Database." + } + + param "resource_group" { + type = string + description = local.description_resource_group + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.sql_databases_with_transparent_data_encryption_disabled_default_action + enum = local.sql_databases_with_transparent_data_encryption_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.sql_databases_with_transparent_data_encryption_disabled_enabled_actions + enum = local.sql_databases_with_transparent_data_encryption_disabled_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected SQL Database ${param.title} with transparent data encryption disabled." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped SQL Database ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "enable_sql_db_tde" = { + label = "Enable transparent data encryption" + value = "enable_sql_db_tde" + style = local.style_alert + pipeline_ref = azure.pipeline.set_sql_db_tde + pipeline_args = { + resource_group = param.resource_group + subscription_id = param.subscription_id + server_name = param.server_name + database_name = param.name + conn = param.conn + status = "Enabled" + } + success_msg = "Enabled transparent data encryption for SQL Database ${param.title}." + error_msg = "Error enabling transparent data encryption for SQL Database ${param.title}." + } + } + } + } +} diff --git a/pipelines/sql/sql_servers_tde_protector_not_encrypted_with_cmk.fp b/pipelines/sql/sql_servers_tde_protector_not_encrypted_with_cmk.fp new file mode 100644 index 0000000..cfde268 --- /dev/null +++ b/pipelines/sql/sql_servers_tde_protector_not_encrypted_with_cmk.fp @@ -0,0 +1,132 @@ +locals { + sql_servers_tde_protector_not_encrypted_with_cmk_query = <<-EOQ + select + concat(id, ' [', subscription_id, '/', resource_group, ']') as title, + name, + resource_group, + subscription_id, + _ctx ->> 'connection_name' as conn + from + azure_sql_server, + jsonb_array_elements(encryption_protector) encryption + where + encryption ->> 'kind' = 'servicemanaged'; + EOQ +} + +variable "sql_servers_tde_protector_not_encrypted_with_cmk_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/SQL" + } +} + +variable "sql_servers_tde_protector_not_encrypted_with_cmk_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/SQL" + } +} + +trigger "query" "detect_and_correct_sql_servers_tde_protector_not_encrypted_with_cmk" { + title = "Detect & correct SQL servers TDE protector not encrypted with CMK" + description = "Detect SQL servers TDE protector not encrypted with CMK." + tags = local.sql_common_tags + + enabled = var.sql_servers_tde_protector_not_encrypted_with_cmk_trigger_enabled + schedule = var.sql_servers_tde_protector_not_encrypted_with_cmk_trigger_schedule + database = var.database + sql = local.sql_servers_tde_protector_not_encrypted_with_cmk_query + + capture "insert" { + pipeline = pipeline.correct_sql_servers_tde_protector_not_encrypted_with_cmk + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_sql_servers_tde_protector_not_encrypted_with_cmk" { + title = "Detect & correct SQL servers TDE protector not encrypted with CMK" + description = "Send notifications for SQL servers TDE protector not encrypted with CMK." + tags = local.sql_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.sql_servers_tde_protector_not_encrypted_with_cmk_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_sql_servers_tde_protector_not_encrypted_with_cmk + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_sql_servers_tde_protector_not_encrypted_with_cmk" { + title = "Correct SQL servers TDE protector not encrypted with CMK" + description = "Encrypt SQL servers TDE protector not encrypted with CMK for servers not encrypted with CMK." + tags = merge(local.sql_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} SQL server(s) TDE not encrypted with CMK." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected SQL server ${each.value.title} TDE not encrypted with CMK." + } +} \ No newline at end of file diff --git a/pipelines/sql/sql_servers_with_auditing_disabled.fp b/pipelines/sql/sql_servers_with_auditing_disabled.fp new file mode 100644 index 0000000..627ea22 --- /dev/null +++ b/pipelines/sql/sql_servers_with_auditing_disabled.fp @@ -0,0 +1,132 @@ +locals { + sql_servers_with_auditing_disabled_query = <<-EOQ + select + concat(id, ' [', subscription_id, '/', resource_group, ']') as title, + name, + resource_group, + subscription_id, + _ctx ->> 'connection_name' as conn + from + azure_sql_server, + jsonb_array_elements(server_audit_policy) audit + where + audit -> 'properties' ->> 'state' = 'Disabled'; + EOQ +} + +variable "sql_servers_with_auditing_disabled_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/SQL" + } +} + +variable "sql_servers_with_auditing_disabled_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/SQL" + } +} + +trigger "query" "detect_and_correct_sql_servers_with_auditing_disabled" { + title = "Detect & correct SQL servers with auditing disabled" + description = "Detect SQL servers with auditing disabled." + tags = local.sql_common_tags + + enabled = var.sql_servers_with_auditing_disabled_trigger_enabled + schedule = var.sql_servers_with_auditing_disabled_trigger_schedule + database = var.database + sql = local.sql_servers_with_auditing_disabled_query + + capture "insert" { + pipeline = pipeline.correct_sql_servers_with_auditing_disabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_sql_servers_with_auditing_disabled" { + title = "Detect & correct SQL servers with auditing disabled" + description = "Detect SQL servers with auditing disabled." + tags = local.sql_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.sql_servers_with_auditing_disabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_sql_servers_with_auditing_disabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_sql_servers_with_auditing_disabled" { + title = "Correct SQL servers with auditing disabled" + description = "Send notifications for SQL servers with auditing disabled." + tags = merge(local.sql_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} SQL server(s) with auditing disabled." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected SQL server ${each.value.title} with auditing disabled." + } +} \ No newline at end of file diff --git a/pipelines/sql/sql_servers_with_auditing_retention_period_less_than_90_days.fp b/pipelines/sql/sql_servers_with_auditing_retention_period_less_than_90_days.fp new file mode 100644 index 0000000..1c6efb5 --- /dev/null +++ b/pipelines/sql/sql_servers_with_auditing_retention_period_less_than_90_days.fp @@ -0,0 +1,132 @@ +locals { + sql_servers_with_auditing_retention_period_less_than_90_days_query = <<-EOQ + select + concat(id, ' [', subscription_id, '/', resource_group, ']') as title, + name, + resource_group, + subscription_id, + _ctx ->> 'connection_name' as conn + from + azure_sql_server, + jsonb_array_elements(server_audit_policy) audit + where + not ((audit -> 'properties' ->> 'retentionDays')::integer >= 90); + EOQ +} + +variable "sql_servers_with_auditing_retention_period_less_than_90_days_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/SQL" + } +} + +variable "sql_servers_with_auditing_retention_period_less_than_90_days_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/SQL" + } +} + +trigger "query" "detect_and_correct_sql_servers_with_auditing_retention_period_less_than_90_days" { + title = "Detect & correct SQL servers with auditing retention period less than 90 days" + description = "Detect SQL servers with auditing retention period less than 90 days." + tags = local.sql_common_tags + + enabled = var.sql_servers_with_auditing_retention_period_less_than_90_days_trigger_enabled + schedule = var.sql_servers_with_auditing_retention_period_less_than_90_days_trigger_schedule + database = var.database + sql = local.sql_servers_with_auditing_retention_period_less_than_90_days_query + + capture "insert" { + pipeline = pipeline.correct_sql_servers_with_auditing_retention_period_less_than_90_days + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_sql_servers_with_auditing_retention_period_less_than_90_days" { + title = "Detect & correct SQL servers with auditing retention period less than 90 days" + description = "Detect SQL servers with auditing retention period less than 90 days." + tags = local.sql_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.sql_servers_with_auditing_retention_period_less_than_90_days_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_sql_servers_with_auditing_retention_period_less_than_90_days + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_sql_servers_with_auditing_retention_period_less_than_90_days" { + title = "Correct SQL servers with auditing retention period less than 90 days" + description = "Send notifications for SQL servers with auditing retention period less than 90 days." + tags = merge(local.sql_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} SQL server(s) with auditing retention period less than 90 days." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected SQL server ${each.value.title} with auditing retention period less than 90 days." + } +} \ No newline at end of file diff --git a/pipelines/sql/sql_servers_with_public_network_access_enabled.fp b/pipelines/sql/sql_servers_with_public_network_access_enabled.fp new file mode 100644 index 0000000..aa1a986 --- /dev/null +++ b/pipelines/sql/sql_servers_with_public_network_access_enabled.fp @@ -0,0 +1,319 @@ +locals { + sql_servers_with_public_network_access_enabled_query = <<-EOQ + select + concat(id, ' [', subscription_id, '/', resource_group, ']') as title, + id as id, + name as name, + resource_group, + subscription_id, + _ctx ->> 'connection_name' as conn + from + azure_sql_server + where + public_network_access = 'Enabled'; + EOQ + + sql_servers_with_public_network_access_enabled_enabled_actions_enum = ["skip", "disable_public_network_access"] + sql_servers_with_public_network_access_enabled_default_action_enum = ["notify", "skip", "disable_public_network_access"] +} + +variable "sql_servers_with_public_network_access_enabled_trigger_enabled" { + type = bool + default = false + description = "If true, the trigger is enabled." + + tags = { + folder = "Advanced/SQL" + } +} + +variable "sql_servers_with_public_network_access_enabled_trigger_schedule" { + type = string + default = "15m" + description = "If the trigger is enabled, run it on this schedule." + + tags = { + folder = "Advanced/SQL" + } +} + +variable "sql_servers_with_public_network_access_enabled_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/SQL" + } +} + +variable "sql_servers_with_public_network_access_enabled_enabled_actions" { + type = list(string) + description = "The list of enabled actions to provide to approvers for selection." + default = ["skip", "disable_public_network_access"] + + tags = { + folder = "Advanced/SQL" + } +} + +trigger "query" "detect_and_correct_sql_servers_with_public_network_access_enabled" { + title = "Detect & correct SQL servers with public network access enabled" + description = "Detect SQL servers with public network access enabled and then disable public network access." + tags = local.sql_common_tags + + enabled = var.sql_servers_with_public_network_access_enabled_trigger_enabled + schedule = var.sql_servers_with_public_network_access_enabled_trigger_schedule + database = var.database + sql = local.sql_servers_with_public_network_access_enabled_query + + capture "insert" { + pipeline = pipeline.correct_sql_servers_with_public_network_access_enabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_sql_servers_with_public_network_access_enabled" { + title = "Detect & correct SQL servers with public network access enabled" + description = "Detect SQL servers with public network access enabled and then disable public network access." + tags = merge(local.sql_common_tags, { recommended = "true" }) + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.sql_servers_with_public_network_access_enabled_default_action + enum = local.sql_servers_with_public_network_access_enabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.sql_servers_with_public_network_access_enabled_enabled_actions + enum = local.sql_servers_with_public_network_access_enabled_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.sql_servers_with_public_network_access_enabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_sql_servers_with_public_network_access_enabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_sql_servers_with_public_network_access_enabled" { + title = "Correct SQL servers with public network access enabled" + description = "Disable public network access for SQL servers with public network access enabled." + tags = merge(local.sql_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + name = string + resource_group = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.sql_servers_with_public_network_access_enabled_default_action + enum = local.sql_servers_with_public_network_access_enabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.sql_servers_with_public_network_access_enabled_enabled_actions + enum = local.sql_servers_with_public_network_access_enabled_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} SQL server(s) with public network access enabled." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.title => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_sql_server_with_public_network_access_enabled + args = { + title = each.value.title + name = each.value.name + resource_group = each.value.resource_group + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_sql_server_with_public_network_access_enabled" { + title = "Correct SQL servers with public network access enabled" + description = "Disable public network access for a SQL servers with public network access enabled." + tags = merge(local.sql_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "name" { + type = string + description = "The name of the SQL Database." + } + + param "resource_group" { + type = string + description = local.description_resource_group + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.sql_servers_with_public_network_access_enabled_default_action + enum = local.sql_servers_with_public_network_access_enabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.sql_servers_with_public_network_access_enabled_enabled_actions + enum = local.sql_servers_with_public_network_access_enabled_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected SQL server ${param.title} with public network access enabled." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped SQL server ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "disable_public_network_access" = { + label = "Disable public network access" + value = "disable_public_network_access" + style = local.style_alert + pipeline_ref = azure.pipeline.update_sql_server_public_network_access + pipeline_args = { + resource_group = param.resource_group + subscription_id = param.subscription_id + server_name = param.name + conn = param.conn + enable_public_network = false + } + success_msg = "Disabled public network access for SQL server ${param.title}." + error_msg = "Error disabling public network access for SQL server ${param.title}." + } + } + } + } +} diff --git a/pipelines/sql/sql_servers_without_active_directory_admin_configured.fp b/pipelines/sql/sql_servers_without_active_directory_admin_configured.fp new file mode 100644 index 0000000..9ae7411 --- /dev/null +++ b/pipelines/sql/sql_servers_without_active_directory_admin_configured.fp @@ -0,0 +1,131 @@ +locals { + sql_servers_without_active_directory_admin_configured_query = <<-EOQ + select + concat(id, ' [', subscription_id, '/', resource_group, ']') as title, + name, + resource_group, + subscription_id, + _ctx ->> 'connection_name' as conn + from + azure_sql_server + where + server_azure_ad_administrator is null; + EOQ +} + +variable "sql_servers_without_active_directory_admin_configured_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/SQL" + } +} + +variable "sql_servers_without_active_directory_admin_configured_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/SQL" + } +} + +trigger "query" "detect_and_correct_sql_servers_without_active_directory_admin_configured" { + title = "Detect & correct SQL servers without active directory admin configured" + description = "Detect SQL servers without active directory admin configured." + tags = local.sql_common_tags + + enabled = var.sql_servers_without_active_directory_admin_configured_trigger_enabled + schedule = var.sql_servers_without_active_directory_admin_configured_trigger_schedule + database = var.database + sql = local.sql_servers_without_active_directory_admin_configured_query + + capture "insert" { + pipeline = pipeline.correct_sql_servers_without_active_directory_admin_configured + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_sql_servers_without_active_directory_admin_configured" { + title = "Detect & correct SQL servers without active directory admin configured" + description = "Send notifications for SQL servers without active directory admin configured." + tags = local.sql_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.sql_servers_without_active_directory_admin_configured_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_sql_servers_without_active_directory_admin_configured + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_sql_servers_without_active_directory_admin_configured" { + title = "Correct SQL servers without active directory admin configured" + description = "Send notifications for SQL servers without active directory admin configured." + tags = merge(local.sql_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} SQL server(s) without active directory admin configured." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected SQL server ${each.value.title} without active directory admin configured." + } +} \ No newline at end of file diff --git a/pipelines/storage/storage.fp b/pipelines/storage/storage.fp new file mode 100644 index 0000000..e367563 --- /dev/null +++ b/pipelines/storage/storage.fp @@ -0,0 +1,5 @@ +locals { + storage_common_tags = merge(local.azure_compliance_common_tags, { + service = "Azure/Storage" + }) +} diff --git a/pipelines/storage/storage_accounts_with_blob_public_access_enabled.fp b/pipelines/storage/storage_accounts_with_blob_public_access_enabled.fp new file mode 100644 index 0000000..f37245a --- /dev/null +++ b/pipelines/storage/storage_accounts_with_blob_public_access_enabled.fp @@ -0,0 +1,322 @@ +locals { + storage_accounts_with_blob_public_access_enabled_query = <<-EOQ + select + concat(id, ' [', subscription_id, '/', resource_group, ']') as title, + id as id, + name, + resource_group, + subscription_id, + _ctx ->> 'connection_name' as conn + from + azure_storage_account + where + allow_blob_public_access; + EOQ + + storage_accounts_with_blob_public_access_enabled_enabled_actions_enum = ["skip", "disable_blob_public_access"] + storage_accounts_with_blob_public_access_enabled_default_action_enum = ["notify", "skip", "disable_blob_public_access"] +} + +variable "storage_accounts_with_blob_public_access_enabled_trigger_enabled" { + type = bool + default = false + description = "If true, the trigger is enabled." + + tags = { + folder = "Advanced/Storage" + } +} + +variable "storage_accounts_with_blob_public_access_enabled_trigger_schedule" { + type = string + default = "15m" + description = "If the trigger is enabled, run it on this schedule." + + tags = { + folder = "Advanced/Storage" + } +} + +variable "storage_accounts_with_blob_public_access_enabled_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/Storage" + } +} + +variable "storage_accounts_with_blob_public_access_enabled_enabled_actions" { + type = list(string) + description = "The list of enabled actions approvers can select." + default = ["skip", "disable_blob_public_access"] + + tags = { + folder = "Advanced/Storage" + } +} + +trigger "query" "detect_and_correct_storage_accounts_with_blob_public_access_enabled" { + title = "Detect & correct Storage Accounts with blob public access enabled" + description = "Detect Storage Accounts with blob public access enabled and then disable blob public access." + tags = local.storage_common_tags + + enabled = var.storage_accounts_with_blob_public_access_enabled_trigger_enabled + schedule = var.storage_accounts_with_blob_public_access_enabled_trigger_schedule + database = var.database + sql = local.storage_accounts_with_blob_public_access_enabled_query + + capture "insert" { + pipeline = pipeline.correct_storage_accounts_with_blob_public_access_enabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_storage_accounts_with_blob_public_access_enabled" { + title = "Detect & correct Storage Accounts with blob public access enabled" + description = "Detect Storage Accounts with blob public access enabled and then disable blob public access." + tags = merge(local.storage_common_tags, { recommended = "true" }) + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.storage_accounts_with_blob_public_access_enabled_default_action + enum = local.storage_accounts_with_blob_public_access_enabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.storage_accounts_with_blob_public_access_enabled_enabled_actions + enum = local.storage_accounts_with_blob_public_access_enabled_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.storage_accounts_with_blob_public_access_enabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_storage_accounts_with_blob_public_access_enabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_storage_accounts_with_blob_public_access_enabled" { + title = "Correct Storage Accounts with blob public access enabled" + description = "Disable blob public access for Storage Accounts with blob public access enabled." + tags = merge(local.storage_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + name = string + resource_group = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.storage_accounts_with_blob_public_access_enabled_default_action + enum = local.storage_accounts_with_blob_public_access_enabled_default_action_enum + } + + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.storage_accounts_with_blob_public_access_enabled_enabled_actions + enum = local.storage_accounts_with_blob_public_access_enabled_enabled_actions_enum + } + + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} Storage Account(s) with blob public access enabled." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.id => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_storage_account_with_blob_public_access_enabled + args = { + title = each.value.title + name = each.value.name + resource_group = each.value.resource_group + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_storage_account_with_blob_public_access_enabled" { + title = "Correct Storage Accouns with blob public access enabled" + description = "Disable blob public access for a Storage Account with blob public access enabled." + tags = merge(local.storage_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "name" { + type = string + description = "The name of the Storage Account." + } + + param "resource_group" { + type = string + description = local.description_resource_group + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.storage_accounts_with_blob_public_access_enabled_default_action + enum = local.storage_accounts_with_blob_public_access_enabled_default_action_enum + } + + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.storage_accounts_with_blob_public_access_enabled_enabled_actions + enum = local.storage_accounts_with_blob_public_access_enabled_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected Storage Account ${param.title} with blob public access enabled." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped Storage Account ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "disable_blob_public_access" = { + label = "Disable blob public access" + value = "disable_blob_public_access" + style = local.style_alert + pipeline_ref = azure.pipeline.update_storage_account_blob_public_access + pipeline_args = { + account_name = param.name + resource_group = param.resource_group + subscription_id = param.subscription_id + conn = param.conn + allow_blob_public_access = false + } + success_msg = "Disabled blob public access for Storage Account ${param.title}." + error_msg = "Error disabling blob public access for Storage Account ${param.title}." + } + } + } + } +} diff --git a/pipelines/storage/storage_accounts_with_blob_service_logging_disabled.fp b/pipelines/storage/storage_accounts_with_blob_service_logging_disabled.fp new file mode 100644 index 0000000..d888353 --- /dev/null +++ b/pipelines/storage/storage_accounts_with_blob_service_logging_disabled.fp @@ -0,0 +1,338 @@ +locals { + storage_accounts_with_blob_service_logging_disabled_query = <<-EOQ + with get_access_key as ( + select + distinct on (id) id, + k ->> 'Value' as access_key + from + azure_storage_account, + jsonb_array_elements(access_keys) as k + order by + id + ) + select + concat(sa.id, ' [', sa.subscription_id, '/', sa.resource_group, ']') as title, + sa.id as id, + k.access_key as access_key, + sa.name, + sa.subscription_id, + sa._ctx ->> 'connection_name' as conn + from + azure_storage_account as sa, + get_access_key as k, + azure_subscription as sub + where + sub.subscription_id = sa.subscription_id + and k.id = sa.id + and ( + not (sa.blob_service_logging ->> 'Read') :: boolean + or not (sa.blob_service_logging ->> 'Write') :: boolean + or not (sa.blob_service_logging ->> 'Delete') :: boolean + ) + EOQ + + storage_accounts_with_blob_service_logging_disabled_enabled_actions_enum = ["skip", "enable_blob_service_logging"] + storage_accounts_with_blob_service_logging_disabled_default_action_enum = ["notify", "skip", "enable_blob_service_logging"] +} + +variable "storage_accounts_with_blob_service_logging_disabled_trigger_enabled" { + type = bool + default = false + description = "If true, the trigger is enabled." + + tags = { + folder = "Advanced/Storage" + } +} + +variable "storage_accounts_with_blob_service_logging_disabled_trigger_schedule" { + type = string + default = "15m" + description = "If the trigger is enabled, run it on this schedule." + + tags = { + folder = "Advanced/Storage" + } +} + +variable "storage_accounts_with_blob_service_logging_disabled_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/Storage" + } +} + +variable "storage_accounts_with_blob_service_logging_disabled_enabled_actions" { + type = list(string) + description = "The list of enabled actions approvers can select." + default = ["skip", "enable_blob_service_logging"] + + tags = { + folder = "Advanced/Storage" + } +} + +trigger "query" "detect_and_correct_storage_accounts_with_blob_service_logging_disabled" { + title = "Detect & correct Storage Accounts with blob service logging disabled" + description = "Detect Storage Accounts with blob service logging disabled and then enable blob service logging." + tags = local.storage_common_tags + + enabled = var.storage_accounts_with_blob_service_logging_disabled_trigger_enabled + schedule = var.storage_accounts_with_blob_service_logging_disabled_trigger_schedule + database = var.database + sql = local.storage_accounts_with_blob_service_logging_disabled_query + + capture "insert" { + pipeline = pipeline.correct_storage_accounts_with_blob_service_logging_disabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_storage_accounts_with_blob_service_logging_disabled" { + title = "Detect & correct Storage Accounts with blob service logging disabled" + description = "Detect Storage Accounts with blob service logging disabled and then enable blob service logging." + tags = merge(local.storage_common_tags, { recommended = "true" }) + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.storage_accounts_with_blob_service_logging_disabled_default_action + enum = local.storage_accounts_with_blob_service_logging_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.storage_accounts_with_blob_service_logging_disabled_enabled_actions + enum = local.storage_accounts_with_blob_service_logging_disabled_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.storage_accounts_with_blob_service_logging_disabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_storage_accounts_with_blob_service_logging_disabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_storage_accounts_with_blob_service_logging_disabled" { + title = "Correct Storage Accounts with blob service logging disabled" + description = "Enable blob service logging for Storage Accounts with blob service logging disabled." + tags = merge(local.storage_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + name = string + subscription_id = string + access_key = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.storage_accounts_with_blob_service_logging_disabled_default_action + enum = local.storage_accounts_with_blob_service_logging_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.storage_accounts_with_blob_service_logging_disabled_enabled_actions + enum = local.storage_accounts_with_blob_service_logging_disabled_enabled_actions_enum + } + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} Storage Account(s) with blob service logging disabled." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.id => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_storage_account_with_blob_service_logging_disabled + args = { + title = each.value.title + name = each.value.name + subscription_id = each.value.subscription_id + access_key = each.value.access_key + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_storage_account_with_blob_service_logging_disabled" { + title = "Correct Storage Account with blob service logging disabled" + description = "Enable blob service logging for Storage Accouns with blob service logging disabled." + tags = merge(local.storage_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "name" { + type = string + description = "The name of the Storage Account." + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "access_key" { + type = string + description = "The access key of the storage account." + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.storage_accounts_with_blob_service_logging_disabled_default_action + enum = local.storage_accounts_with_blob_service_logging_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.storage_accounts_with_blob_service_logging_disabled_enabled_actions + enum = local.storage_accounts_with_blob_service_logging_disabled_enabled_actions_enum + } + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected Storage Account ${param.title} with blob service logging disabled." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped Storage Account ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "enable_blob_service_logging" = { + label = "Enable blob service logging" + value = "enable_blob_service_logging" + style = local.style_alert + pipeline_ref = azure.pipeline.update_storage_account_logging + pipeline_args = { + account_name = param.name + subscription_id = param.subscription_id + access_key = param.access_key + conn = param.conn + services = "b" + log = "rwd" + retention = 90 + } + success_msg = "Enabled blob service logging for Storage Account ${param.title}." + error_msg = "Error enabling blob service logging for Storage Account ${param.title}." + } + } + } + } +} + diff --git a/pipelines/storage/storage_accounts_with_blob_soft_delete_disabled.fp b/pipelines/storage/storage_accounts_with_blob_soft_delete_disabled.fp new file mode 100644 index 0000000..4d8b398 --- /dev/null +++ b/pipelines/storage/storage_accounts_with_blob_soft_delete_disabled.fp @@ -0,0 +1,325 @@ +locals { + storage_accounts_with_blob_soft_delete_disabled_query = <<-EOQ + select + concat(sa.id, ' [', sa.subscription_id, '/', sa.resource_group, ']') as title, + sa.id as id, + sa.name, + sa.resource_group, + sa.subscription_id, + sa._ctx ->> 'connection_name' as conn + from + azure_storage_account as sa, + azure_subscription as sub + where + sub.subscription_id = sa.subscription_id + and not blob_soft_delete_enabled; + EOQ + + storage_accounts_with_blob_soft_delete_disabled_enabled_actions_enum = ["skip", "enable_blob_soft_delete"] + storage_accounts_with_blob_soft_delete_disabled_default_action_enum = ["notify", "skip", "enable_blob_soft_delete"] +} + +variable "storage_accounts_with_blob_soft_delete_disabled_trigger_enabled" { + type = bool + default = false + description = "If true, the trigger is enabled." + + tags = { + folder = "Advanced/Storage" + } +} + +variable "storage_accounts_with_blob_soft_delete_disabled_trigger_schedule" { + type = string + default = "15m" + description = "If the trigger is enabled, run it on this schedule." + + tags = { + folder = "Advanced/Storage" + } +} + +variable "storage_accounts_with_blob_soft_delete_disabled_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/Storage" + } +} + +variable "storage_accounts_with_blob_soft_delete_disabled_enabled_actions" { + type = list(string) + description = "The list of enabled actions approvers can select." + default = ["skip", "enable_blob_soft_delete"] + + tags = { + folder = "Advanced/Storage" + } +} + +trigger "query" "detect_and_correct_storage_accounts_with_blob_soft_delete_disabled" { + title = "Detect & correct Storage Accounts with blob soft delete disabled" + description = "Detect Storage Accounts with blob soft delete disabled and then enable blob soft delete." + tags = local.storage_common_tags + + enabled = var.storage_accounts_with_blob_soft_delete_disabled_trigger_enabled + schedule = var.storage_accounts_with_blob_soft_delete_disabled_trigger_schedule + database = var.database + sql = local.storage_accounts_with_blob_soft_delete_disabled_query + + capture "insert" { + pipeline = pipeline.correct_storage_accounts_with_blob_soft_delete_disabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_storage_accounts_with_blob_soft_delete_disabled" { + title = "Detect & correct Storage Accounts with blob soft delete disabled" + description = "Detect Storage Accounts with blob soft delete disabled and then enable blob soft delete." + tags = merge(local.storage_common_tags, { recommended = "true" }) + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.storage_accounts_with_blob_soft_delete_disabled_default_action + enum = local.storage_accounts_with_blob_soft_delete_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.storage_accounts_with_blob_soft_delete_disabled_enabled_actions + enum = local.storage_accounts_with_blob_soft_delete_disabled_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.storage_accounts_with_blob_soft_delete_disabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_storage_accounts_with_blob_soft_delete_disabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_storage_accounts_with_blob_soft_delete_disabled" { + title = "Correct Storage Accounts with blob soft delete disabled" + description = "Enable blob soft delete for Storage Accounts with blob soft delete disabled." + tags = merge(local.storage_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + name = string + resource_group = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.storage_accounts_with_blob_soft_delete_disabled_default_action + enum = local.storage_accounts_with_blob_soft_delete_disabled_default_action_enum + } + + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.storage_accounts_with_blob_soft_delete_disabled_enabled_actions + enum = local.storage_accounts_with_blob_soft_delete_disabled_enabled_actions_enum + } + + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} Storage Account(s) with blob soft delete disabled." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.id => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_storage_account_with_blob_soft_delete_disabled + args = { + title = each.value.title + name = each.value.name + resource_group = each.value.resource_group + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_storage_account_with_blob_soft_delete_disabled" { + title = "Correct Storage Account with blob soft delete disabled" + description = "Enable blob soft delete for a Storage Account with blob soft delete disabled." + tags = merge(local.storage_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "name" { + type = string + description = "The name of the Storage Account." + } + + param "resource_group" { + type = string + description = local.description_resource_group + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.storage_accounts_with_blob_soft_delete_disabled_default_action + enum = local.storage_accounts_with_blob_soft_delete_disabled_default_action_enum + } + + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.storage_accounts_with_blob_soft_delete_disabled_enabled_actions + enum = local.storage_accounts_with_blob_soft_delete_disabled_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected Storage Account ${param.title} with blob soft delete disabled." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped Storage Account ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "enable_blob_soft_delete" = { + label = "Enable blob soft delete" + value = "enable_blob_soft_delete" + style = local.style_alert + pipeline_ref = azure.pipeline.update_storage_account_blob_service_properties + pipeline_args = { + account_name = param.name + resource_group = param.resource_group + subscription_id = param.subscription_id + conn = param.conn + enable_container_delete_retention = true + container_delete_retention_days = 30 + } + success_msg = "Enabled blob soft delete for Storage Account ${param.title}." + error_msg = "Error enabling blob soft delete for Storage Account ${param.title}." + } + } + } + } +} diff --git a/pipelines/storage/storage_accounts_with_default_network_access_rule_allowed.fp b/pipelines/storage/storage_accounts_with_default_network_access_rule_allowed.fp new file mode 100644 index 0000000..eacb6a3 --- /dev/null +++ b/pipelines/storage/storage_accounts_with_default_network_access_rule_allowed.fp @@ -0,0 +1,329 @@ +locals { + storage_accounts_with_default_network_access_rule_allowed_query = <<-EOQ + select + concat(sa.id, ' [', sa.resource_group, '/', sa.subscription_id, ']') as title, + sa.id as id, + sa.name, + sa.resource_group, + sa.subscription_id, + sa._ctx ->> 'connection_name' as conn + from + azure_storage_account as sa, + azure_subscription as sub + where + sa.network_rule_default_action = 'Allow' + and sub.subscription_id = sa.subscription_id; + EOQ + + storage_accounts_with_default_network_access_rule_allowed_enabled_actions_enum = ["skip", "update_default_action_deny"] + storage_accounts_with_default_network_access_rule_allowed_default_action_enum = ["notify", "skip", "update_default_action_deny"] +} + +variable "storage_accounts_with_default_network_access_rule_allowed_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/Storage" + } +} + +variable "storage_accounts_with_default_network_access_rule_allowed_trigger_schedule" { + type = string + default = "15m" + description = "If the trigger is enabled, run it on this schedule." + + tags = { + folder = "Advanced/Storage" + } +} + +variable "storage_accounts_with_default_network_access_rule_allowed_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/Storage" + } +} + +variable "storage_accounts_with_default_network_access_rule_allowed_enabled_actions" { + type = list(string) + description = "The list of enabled actions to provide to approvers for selection." + default = ["skip", "update_default_action_deny"] + + tags = { + folder = "Advanced/Storage" + } +} + +trigger "query" "detect_and_correct_storage_accounts_with_default_network_access_rule_allowed" { + title = "Detect & correct Storage Accounts with default network access rule set to Allow" + description = "Detect Storage Accounts with default network access rule set to Allow and runs your chosen action." + tags = local.storage_common_tags + + enabled = var.storage_accounts_with_default_network_access_rule_allowed_trigger_enabled + schedule = var.storage_accounts_with_default_network_access_rule_allowed_trigger_schedule + database = var.database + sql = local.storage_accounts_with_default_network_access_rule_allowed_query + + capture "insert" { + pipeline = pipeline.correct_storage_accounts_with_default_network_access_rule_allowed + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_storage_accounts_with_default_network_access_rule_allowed" { + title = "Detect & correct Storage Accounts with default network access rule set to Allow" + description = "Detect Storage Accounts with default network access rule set to Allow and runs your chosen action." + tags = merge(local.storage_common_tags, { recommended = "true" }) + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.storage_accounts_with_default_network_access_rule_allowed_default_action + enum = local.storage_accounts_with_default_network_access_rule_allowed_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.storage_accounts_with_default_network_access_rule_allowed_enabled_actions + enum = local.storage_accounts_with_default_network_access_rule_allowed_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.storage_accounts_with_default_network_access_rule_allowed_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_storage_accounts_with_default_network_access_rule_allowed + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_storage_accounts_with_default_network_access_rule_allowed" { + title = "Correct Storage Accounts with default network access rule set to Allow" + description = "Runs corrective action on a collection of Storage Accounts with default network access rule set to Allow." + tags = merge(local.storage_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + name = string + resource_group = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.storage_accounts_with_default_network_access_rule_allowed_default_action + enum = local.storage_accounts_with_default_network_access_rule_allowed_default_action_enum + } + + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.storage_accounts_with_default_network_access_rule_allowed_enabled_actions + enum = local.storage_accounts_with_default_network_access_rule_allowed_enabled_actions_enum + } + + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} Storage Accounts with default network access rule set to Allow." + } + + step "transform" "items_by_id" { + value = { for row in param.items : row.id => row } + } + + step "pipeline" "correct_item" { + for_each = step.transform.items_by_id.value + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_storage_account_with_default_network_access_rule_allowed + args = { + title = each.value.title + name = each.value.name + resource_group = each.value.resource_group + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_storage_account_with_default_network_access_rule_allowed" { + title = "Correct one Storage Account with default network access rule set to Allow" + description = "Runs corrective action on a single Storage Account with default network access rule set to Allow." + tags = merge(local.storage_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "name" { + type = string + description = "The name of the Storage Account." + } + + param "resource_group" { + type = string + description = local.description_resource_group + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.storage_accounts_with_default_network_access_rule_allowed_default_action + enum = local.storage_accounts_with_default_network_access_rule_allowed_default_action_enum + } + + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.storage_accounts_with_default_network_access_rule_allowed_enabled_actions + enum = local.storage_accounts_with_default_network_access_rule_allowed_enabled_actions_enum + } + + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected Storage Account ${param.title} with default network access rule set to Allow." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped Storage Account ${param.title} with default network access rule set to Allow." + } + success_msg = "" + error_msg = "" + }, + "update_default_action_deny" = { + label = "Update default action to deny" + value = "update_default_action_deny" + style = local.style_alert + pipeline_ref = azure.pipeline.update_storage_account_default_action + pipeline_args = { + account_name = param.name + resource_group = param.resource_group + subscription_id = param.subscription_id + conn = param.conn + default_action = "Deny" + } + success_msg = "Updated default action to Deny for Storage Account ${param.title}." + error_msg = "Error updating default action to Deny for Storage Account ${param.title}." + } + } + } + } +} diff --git a/pipelines/storage/storage_accounts_with_encryption_at_rest_using_cmk_disabled.fp b/pipelines/storage/storage_accounts_with_encryption_at_rest_using_cmk_disabled.fp new file mode 100644 index 0000000..4437841 --- /dev/null +++ b/pipelines/storage/storage_accounts_with_encryption_at_rest_using_cmk_disabled.fp @@ -0,0 +1,135 @@ + +locals { + storage_accounts_with_encryption_at_rest_using_cmk_disabled_query = <<-EOQ + select + concat(id, ' [', subscription_id, '/', resource_group, ']') as title, + id as id, + name, + resource_group, + subscription_id, + _ctx ->> 'connection_name' as conn + from + azure_storage_account + where + encryption_key_source = 'Microsoft.Storage'; + EOQ + +} + +variable "storage_accounts_with_encryption_at_rest_using_cmk_disabled_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/Storage" + } +} + +variable "storage_accounts_with_encryption_at_rest_using_cmk_disabled_trigger_schedule" { + type = string + default = "15m" + description = "If the trigger is enabled, run it on this schedule." + + tags = { + folder = "Advanced/Storage" + } +} + +trigger "query" "detect_and_correct_storage_accounts_with_encryption_at_rest_using_cmk_disabled" { + title = "Detect & correct Storage accounts with encryption at rest using CMK disabled" + description = "Detect Storage accounts with encryption at rest using CMK disabled." + + tags = local.storage_common_tags + + enabled = var.storage_accounts_with_encryption_at_rest_using_cmk_disabled_trigger_enabled + schedule = var.storage_accounts_with_encryption_at_rest_using_cmk_disabled_trigger_schedule + database = var.database + sql = local.storage_accounts_with_encryption_at_rest_using_cmk_disabled_query + + capture "insert" { + pipeline = pipeline.correct_storage_accounts_with_encryption_at_rest_using_cmk_disabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_storage_accounts_with_encryption_at_rest_using_cmk_disabled" { + title = "Detect & correct Storage accounts with encryption at rest using CMK disabled" + description = "Detect Storage accounts with encryption at rest using CMK disabled." + + tags = local.storage_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.storage_accounts_with_encryption_at_rest_using_cmk_disabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_storage_accounts_with_encryption_at_rest_using_cmk_disabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_storage_accounts_with_encryption_at_rest_using_cmk_disabled" { + title = "Correct Storage accounts with encryption at rest using CMK disabled" + description = "Executes corrective actions on Storage accounts with encryption at rest using CMK disabled." + tags = merge(local.storage_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + conn = string + })) + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} Storage account(s) with encryption at rest using CMK disabled." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected Storage accoun ${each.value.title} with encryption at rest using CMK disabled." + } +} diff --git a/pipelines/storage/storage_accounts_with_infrastructure_encryption_disabled.fp b/pipelines/storage/storage_accounts_with_infrastructure_encryption_disabled.fp new file mode 100644 index 0000000..997ed6f --- /dev/null +++ b/pipelines/storage/storage_accounts_with_infrastructure_encryption_disabled.fp @@ -0,0 +1,132 @@ +locals { + storage_accounts_with_infrastructure_encryption_disabled_query = <<-EOQ + select + concat(id, ' [', subscription_id, '/', resource_group, ']') as title, + id as id, + name, + resource_group, + subscription_id, + _ctx ->> 'connection_name' as conn + from + azure_storage_account + where + not require_infrastructure_encryption; + EOQ +} + +variable "storage_accounts_with_infrastructure_encryption_disabled_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/Storage" + } +} + +variable "storage_accounts_with_infrastructure_encryption_disabled_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/Storage" + } +} + +trigger "query" "detect_and_correct_storage_accounts_with_infrastructure_encryption_disabled" { + title = "Detect & correct Storage Accounts with infrastructure encryption disabled" + description = "Detect Storage Accounts with infrastructure encryption disabled." + tags = local.storage_common_tags + + enabled = var.storage_accounts_with_infrastructure_encryption_disabled_trigger_enabled + schedule = var.storage_accounts_with_infrastructure_encryption_disabled_trigger_schedule + database = var.database + sql = local.storage_accounts_with_infrastructure_encryption_disabled_query + + capture "insert" { + pipeline = pipeline.correct_storage_accounts_with_infrastructure_encryption_disabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_storage_accounts_with_infrastructure_encryption_disabled" { + title = "Detect & correct Storage Accounts with infrastructure encryption disabled" + description = "Detect Storage Accounts with infrastructure encryption disabled." + tags = local.storage_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.storage_accounts_with_infrastructure_encryption_disabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_storage_accounts_with_infrastructure_encryption_disabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_storage_accounts_with_infrastructure_encryption_disabled" { + title = "Correct Storage Accounts with infrastructure encryption disabled." + description = "Send notifications for Storage Accounts with infrastructure encryption disabled." + tags = merge(local.storage_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} Storage Account(s) with infrastructure encryption disabled." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected Storage Account ${each.value.title} with infrastructure encryption disabled." + } +} diff --git a/pipelines/storage/storage_accounts_with_no_min_tls_1_2.fp b/pipelines/storage/storage_accounts_with_no_min_tls_1_2.fp new file mode 100644 index 0000000..97356bd --- /dev/null +++ b/pipelines/storage/storage_accounts_with_no_min_tls_1_2.fp @@ -0,0 +1,322 @@ +locals { + storage_accounts_with_no_min_tls_1_2_query = <<-EOQ + select + concat(sa.id, ' [', sa.subscription_id, '/', sa.resource_group, ']') as title, + sa.id as id, + sa.name, + sa.resource_group, + sa.subscription_id, + sa._ctx ->> 'connection_name' as conn + from + azure_storage_account as sa, + azure_subscription as sub + where + sa.minimum_tls_version <> 'TLS1_2' + and sub.subscription_id = sa.subscription_id; + EOQ + + storage_accounts_with_no_min_tls_1_2_enabled_actions_enum = ["skip", "enable_min_tls_1_2"] + storage_accounts_with_no_min_tls_1_2_default_action_enum = ["notify", "skip", "enable_min_tls_1_2"] +} + +variable "storage_accounts_with_no_min_tls_1_2_trigger_enabled" { + type = bool + default = false + description = "If true, the trigger is enabled." + + tags = { + folder = "Advanced/Storage" + } +} + +variable "storage_accounts_with_no_min_tls_1_2_trigger_schedule" { + type = string + default = "15m" + description = "If the trigger is enabled, run it on this schedule." + + tags = { + folder = "Advanced/Storage" + } +} + +variable "storage_accounts_with_no_min_tls_1_2_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/Storage" + } +} + +variable "storage_accounts_with_no_min_tls_1_2_enabled_actions" { + type = list(string) + description = "The list of enabled actions to provide to approvers for selection." + default = ["skip", "enable_min_tls_1_2"] + + tags = { + folder = "Advanced/Storage" + } +} + +trigger "query" "detect_and_correct_storage_accounts_with_no_min_tls_1_2" { + title = "Detect & correct Storage Accounts with minimum TLS version less than 1.2" + description = "Detect Storage Accounts with minimum TLS version less than 1.2 and then enable 1.2 TLS version." + tags = local.storage_common_tags + + enabled = var.storage_accounts_with_no_min_tls_1_2_trigger_enabled + schedule = var.storage_accounts_with_no_min_tls_1_2_trigger_schedule + database = var.database + sql = local.storage_accounts_with_no_min_tls_1_2_query + + capture "insert" { + pipeline = pipeline.correct_storage_accounts_with_no_min_tls_1_2 + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_storage_accounts_with_no_min_tls_1_2" { + title = "Detect & correct Storage Accounts with minimum TLS version less than 1.2" + description = "Detect Storage Accounts with minimum TLS version less than 1.2 and then enable 1.2 TLS version." + tags = merge(local.storage_common_tags, { recommended = "true" }) + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.storage_accounts_with_no_min_tls_1_2_default_action + enum = local.storage_accounts_with_no_min_tls_1_2_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.storage_accounts_with_no_min_tls_1_2_enabled_actions + enum = local.storage_accounts_with_no_min_tls_1_2_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.storage_accounts_with_no_min_tls_1_2_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_storage_accounts_with_no_min_tls_1_2 + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_storage_accounts_with_no_min_tls_1_2" { + title = "Correct Storage Accounts with minimum TLS version less than 1.2" + description = "Enable 1.2 TLS version for Storage Accounts with minimum TLS version less than 1.2" + tags = merge(local.storage_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + name = string + resource_group = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.storage_accounts_with_no_min_tls_1_2_default_action + enum = local.storage_accounts_with_no_min_tls_1_2_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.storage_accounts_with_no_min_tls_1_2_enabled_actions + enum = local.storage_accounts_with_no_min_tls_1_2_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} Storage Account(s) with minimum TLS version less than 1.2." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.id => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_storage_account_with_no_min_tls_1_2 + args = { + title = each.value.title + name = each.value.name + resource_group = each.value.resource_group + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_storage_account_with_no_min_tls_1_2" { + title = "Correct Storage Account with minimum TLS version less than 1.2" + description = "Enable 1.2 TLS version for a Storage Account with minimum TLS version less than 1.2" + tags = merge(local.storage_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "name" { + type = string + description = "The name of the Storage Account." + } + + param "resource_group" { + type = string + description = local.description_resource_group + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.storage_accounts_with_no_min_tls_1_2_default_action + enum = local.storage_accounts_with_no_min_tls_1_2_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.storage_accounts_with_no_min_tls_1_2_enabled_actions + enum = local.storage_accounts_with_no_min_tls_1_2_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected Storage Account ${param.title} with minimum TLS version less than 1.2." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped Storage Account ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "enable_min_tls_1_2" = { + label = "Enable minimum TLS 1.2" + value = "enable_min_tls_1_2" + style = local.style_alert + pipeline_ref = azure.pipeline.update_storage_account_minimum_tls + pipeline_args = { + account_name = param.name + resource_group = param.resource_group + subscription_id = param.subscription_id + conn = param.conn + minimum_tls_version = "TLS1_2" + } + success_msg = "Enabled minimum TLS 1.2 for Storage Account ${param.title}." + error_msg = "Error enabling minimum TLS 1.2 for Storage Account ${param.title}." + } + } + } + } +} + diff --git a/pipelines/storage/storage_accounts_with_public_access_enabled.fp b/pipelines/storage/storage_accounts_with_public_access_enabled.fp new file mode 100644 index 0000000..67ba891 --- /dev/null +++ b/pipelines/storage/storage_accounts_with_public_access_enabled.fp @@ -0,0 +1,319 @@ +locals { + storage_accounts_with_public_access_enabled_query = <<-EOQ + select + concat(sa.id, ' [', sa.subscription_id, '/', sa.resource_group, ']') as title, + sa.id as id, + sa.name, + sa.resource_group, + sa.subscription_id, + sa._ctx ->> 'connection_name' as conn + from + azure_storage_account as sa + where + sa.public_network_access = 'Enabled'; + EOQ + + storage_accounts_with_public_access_enabled_enabled_actions_enum = ["skip", "disable_public_network_access"] + storage_accounts_with_public_access_enabled_default_action_enum = ["notify", "skip", "disable_public_network_access"] +} + +variable "storage_accounts_with_public_access_enabled_trigger_enabled" { + type = bool + default = false + description = "If true, the trigger is enabled." + + tags = { + folder = "Advanced/Storage" + } +} + +variable "storage_accounts_with_public_access_enabled_trigger_schedule" { + type = string + default = "15m" + description = "If the trigger is enabled, run it on this schedule." + + tags = { + folder = "Advanced/Storage" + } +} + +variable "storage_accounts_with_public_access_enabled_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/Storage" + } +} + +variable "storage_accounts_with_public_access_enabled_enabled_actions" { + type = list(string) + description = "The list of enabled actions to provide to approvers for selection." + default = ["skip", "disable_public_network_access"] + + tags = { + folder = "Advanced/Storage" + } +} + +trigger "query" "detect_and_correct_storage_accounts_with_public_access_enabled" { + title = "Detect & correct Storage Accounts with public access enabled" + description = "Detect publicly accessible Storage Accounts and then disable public access." + tags = local.storage_common_tags + + enabled = var.storage_accounts_with_public_access_enabled_trigger_enabled + schedule = var.storage_accounts_with_public_access_enabled_trigger_schedule + database = var.database + sql = local.storage_accounts_with_public_access_enabled_query + + capture "insert" { + pipeline = pipeline.correct_storage_accounts_with_public_access_enabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_storage_accounts_with_public_access_enabled" { + title = "Detect & correct Storage Accounts with public access enabled" + description = "Detect publicly accessible Storage Accounts and then disable public access." + tags = merge(local.storage_common_tags, { recommended = "true" }) + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.storage_accounts_with_public_access_enabled_default_action + enum = local.storage_accounts_with_public_access_enabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.storage_accounts_with_public_access_enabled_enabled_actions + enum = local.storage_accounts_with_public_access_enabled_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.storage_accounts_with_public_access_enabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_storage_accounts_with_public_access_enabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_storage_accounts_with_public_access_enabled" { + title = "Correct Storage Accounts with public access enabled" + description = "Disable public access for publicly accessible Storage Accounts." + tags = merge(local.storage_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + name = string + resource_group = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.storage_accounts_with_public_access_enabled_default_action + enum = local.storage_accounts_with_public_access_enabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.storage_accounts_with_public_access_enabled_enabled_actions + enum = local.storage_accounts_with_public_access_enabled_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} publicly accessible Storage Account(s)." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.id => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_storage_account_with_public_access_enabled + args = { + title = each.value.title + name = each.value.name + resource_group = each.value.resource_group + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_storage_account_with_public_access_enabled" { + title = "Correct publicly accessible Storage Account" + description = "Disable public access for a publicly accessible Storage Account." + tags = merge(local.storage_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "name" { + type = string + description = "The name of the Storage Account." + } + + param "resource_group" { + type = string + description = local.description_resource_group + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.storage_accounts_with_public_access_enabled_default_action + enum = local.storage_accounts_with_public_access_enabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.storage_accounts_with_public_access_enabled_enabled_actions + enum = local.storage_accounts_with_public_access_enabled_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected publicly accessible Storage Account ${param.title}." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped Storage Account ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "disable_public_network_access" = { + label = "Disable public access" + value = "disable_public_network_access" + style = local.style_alert + pipeline_ref = azure.pipeline.update_storage_account_public_network_access + pipeline_args = { + account_name = param.name + resource_group = param.resource_group + subscription_id = param.subscription_id + conn = param.conn + public_network_access = false + } + success_msg = "Disabled public access for Storage Account ${param.title}." + error_msg = "Error disabling public access for Storage Account ${param.title}." + } + } + } + } +} diff --git a/pipelines/storage/storage_accounts_with_queue_service_logging_disabled.fp b/pipelines/storage/storage_accounts_with_queue_service_logging_disabled.fp new file mode 100644 index 0000000..8471213 --- /dev/null +++ b/pipelines/storage/storage_accounts_with_queue_service_logging_disabled.fp @@ -0,0 +1,341 @@ +locals { + storage_accounts_with_queue_service_logging_disabled_query = <<-EOQ + with get_access_key as ( + select + distinct on (id) id, + k ->> 'Value' as access_key + from + azure_storage_account, + jsonb_array_elements(access_keys) as k + order by + id + ) + select + concat(sa.id, ' [', sa.resource_group, '/', sa.subscription_id, ']') as title, + sa.id as id, + k.access_key as access_key, + sa.name, + sa.subscription_id, + sa._ctx ->> 'connection_name' as conn + from + azure_storage_account as sa, + get_access_key as k, + azure_subscription as sub + where + sub.subscription_id = sa.subscription_id + and k.id = sa.id + and ( + not queue_logging_read + or not queue_logging_write + or not queue_logging_delete + ) + EOQ + + storage_accounts_with_queue_service_logging_disabled_enabled_actions_enum = ["skip", "enable_queue_service_logging"] + storage_accounts_with_queue_service_logging_disabled_default_action_enum = ["notify", "skip", "enable_queue_service_logging"] +} + +variable "storage_accounts_with_queue_service_logging_disabled_trigger_enabled" { + type = bool + default = false + description = "If true, the trigger is enabled." + + tags = { + folder = "Advanced/Storage" + } +} + +variable "storage_accounts_with_queue_service_logging_disabled_trigger_schedule" { + type = string + default = "15m" + description = "If the trigger is enabled, run it on this schedule." + + tags = { + folder = "Advanced/Storage" + } +} + +variable "storage_accounts_with_queue_service_logging_disabled_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/Storage" + } +} + +variable "storage_accounts_with_queue_service_logging_disabled_enabled_actions" { + type = list(string) + description = "The list of enabled actions to provide to approvers for selection." + default = ["skip", "enable_queue_service_logging"] + + tags = { + folder = "Advanced/Storage" + } +} + +trigger "query" "detect_and_correct_storage_accounts_with_queue_service_logging_disabled" { + title = "Detect & correct Storage Accounts with queue service logging disabled" + description = "Detect Storage Accounts with queue service logging disabled and enable queue service logging." + tags = local.storage_common_tags + + enabled = var.storage_accounts_with_queue_service_logging_disabled_trigger_enabled + schedule = var.storage_accounts_with_queue_service_logging_disabled_trigger_schedule + database = var.database + sql = local.storage_accounts_with_queue_service_logging_disabled_query + + capture "insert" { + pipeline = pipeline.correct_storage_accounts_with_queue_service_logging_disabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_storage_accounts_with_queue_service_logging_disabled" { + title = "Detect & correct Storage Accounts with queue service logging disabled" + description = "Detect Storage Accounts with queue service logging disabled and enable queue service logging." + tags = merge(local.storage_common_tags, { recommended = "true" }) + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.storage_accounts_with_queue_service_logging_disabled_default_action + enum = local.storage_accounts_with_queue_service_logging_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.storage_accounts_with_queue_service_logging_disabled_enabled_actions + enum = local.storage_accounts_with_queue_service_logging_disabled_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.storage_accounts_with_queue_service_logging_disabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_storage_accounts_with_queue_service_logging_disabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_storage_accounts_with_queue_service_logging_disabled" { + title = "Correct Storage Accounts with queue service logging disabled" + description = "Enable queue service logging for Storage Accounts with queue service logging disabled." + tags = merge(local.storage_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + name = string + subscription_id = string + access_key = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.storage_accounts_with_queue_service_logging_disabled_default_action + enum = local.storage_accounts_with_queue_service_logging_disabled_default_action_enum + } + + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.storage_accounts_with_queue_service_logging_disabled_enabled_actions + enum = local.storage_accounts_with_queue_service_logging_disabled_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} Storage Account(s) with queue service logging disabled." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.id => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_storage_account_with_queue_service_logging_disabled + args = { + title = each.value.title + name = each.value.name + subscription_id = each.value.subscription_id + access_key = each.value.access_key + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_storage_account_with_queue_service_logging_disabled" { + title = "Correct Storage Account with queue service logging disabled" + description = "Enable queue service logging for a Storage Account with queue service logging disabled." + tags = merge(local.storage_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "name" { + type = string + description = "The name of the Storage Account." + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "access_key" { + type = string + description = "The access key of the storage account." + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.storage_accounts_with_queue_service_logging_disabled_default_action + enum = local.storage_accounts_with_queue_service_logging_disabled_default_action_enum + } + + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.storage_accounts_with_queue_service_logging_disabled_enabled_actions + enum = local.storage_accounts_with_queue_service_logging_disabled_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected Storage Account ${param.title} with queue service logging disabled." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped Storage Account ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "enable_queue_service_logging" = { + label = "Enable queue service logging" + value = "enable_queue_service_logging" + style = local.style_alert + pipeline_ref = azure.pipeline.update_storage_account_logging + pipeline_args = { + account_name = param.name + subscription_id = param.subscription_id + access_key = param.access_key + conn = param.conn + services = "q" + log = "rwd" + retention = 90 + } + success_msg = "Enabled queue service logging for Storage Account ${param.title}." + error_msg = "Error enabling queue service logging for Storage Account ${param.title}." + } + } + } + } +} diff --git a/pipelines/storage/storage_accounts_with_secure_transfer_required_disabled.fp b/pipelines/storage/storage_accounts_with_secure_transfer_required_disabled.fp new file mode 100644 index 0000000..2870b14 --- /dev/null +++ b/pipelines/storage/storage_accounts_with_secure_transfer_required_disabled.fp @@ -0,0 +1,324 @@ +locals { + storage_accounts_with_secure_transfer_required_disabled_query = <<-EOQ + select + concat(sa.id, ' [', sa.resource_group, '/', sa.subscription_id, ']') as title, + sa.id as id, + sa.name, + sa.resource_group, + sa.subscription_id, + sa._ctx ->> 'connection_name' as conn + from + azure_storage_account as sa, + azure_subscription as sub + where + not enable_https_traffic_only; + EOQ + + storage_accounts_with_secure_transfer_required_disabled_enabled_actions_enum = ["skip", "enable_secure_transfer"] + storage_accounts_with_secure_transfer_required_disabled_default_action_enum = ["notify", "skip", "enable_secure_transfer"] +} + +variable "storage_accounts_with_secure_transfer_required_disabled_trigger_enabled" { + type = bool + default = false + description = "If true, the trigger is enabled." + + tags = { + folder = "Advanced/Storage" + } +} + +variable "storage_accounts_with_secure_transfer_required_disabled_trigger_schedule" { + type = string + default = "15m" + description = "If the trigger is enabled, run it on this schedule." + + tags = { + folder = "Advanced/Storage" + } +} + +variable "storage_accounts_with_secure_transfer_required_disabled_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/Storage" + } +} + +variable "storage_accounts_with_secure_transfer_required_disabled_enabled_actions" { + type = list(string) + description = "The list of enabled actions to provide to approvers for selection." + default = ["skip", "enable_secure_transfer"] + + tags = { + folder = "Advanced/Storage" + } +} + +trigger "query" "detect_and_correct_storage_accounts_with_secure_transfer_required_disabled" { + title = "Detect & correct Storage Accounts with secure transfer required disabled" + description = "Detect Storage Accounts with secure transfer required disabled and runs your chosen action." + tags = local.storage_common_tags + + enabled = var.storage_accounts_with_secure_transfer_required_disabled_trigger_enabled + schedule = var.storage_accounts_with_secure_transfer_required_disabled_trigger_schedule + database = var.database + sql = local.storage_accounts_with_secure_transfer_required_disabled_query + + capture "insert" { + pipeline = pipeline.correct_storage_accounts_with_secure_transfer_required_disabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_storage_accounts_with_secure_transfer_required_disabled" { + title = "Detect & correct Storage Accounts with secure transfer required disabled" + description = "Detect Storage Accounts with secure transfer required disabled and runs your chosen action." + tags = merge(local.storage_common_tags, { recommended = "true" }) + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.storage_accounts_with_secure_transfer_required_disabled_default_action + enum = local.storage_accounts_with_secure_transfer_required_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.storage_accounts_with_secure_transfer_required_disabled_enabled_actions + enum = local.storage_accounts_with_secure_transfer_required_disabled_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.storage_accounts_with_secure_transfer_required_disabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_storage_accounts_with_secure_transfer_required_disabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_storage_accounts_with_secure_transfer_required_disabled" { + title = "Correct Storage Accounts with secure transfer required disabled" + description = "Runs corrective action on a collection of Storage Accounts with secure transfer required disabled." + tags = merge(local.storage_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + name = string + resource_group = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.storage_accounts_with_secure_transfer_required_disabled_default_action + enum = local.storage_accounts_with_secure_transfer_required_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.storage_accounts_with_secure_transfer_required_disabled_enabled_actions + enum = local.storage_accounts_with_secure_transfer_required_disabled_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} Storage Accounts with secure transfer required disabled." + } + + step "transform" "items_by_id" { + value = { for row in param.items : row.id => row } + } + + step "pipeline" "correct_item" { + for_each = step.transform.items_by_id.value + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_storage_account_with_secure_transfer_required_disabled + args = { + title = each.value.title + name = each.value.name + resource_group = each.value.resource_group + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_storage_account_with_secure_transfer_required_disabled" { + title = "Correct one Storage Account with secure transfer required disabled" + description = "Runs corrective action on a single Storage Account with secure transfer required disabled." + tags = merge(local.storage_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "name" { + type = string + description = "The name of the Storage Account." + } + + param "resource_group" { + type = string + description = local.description_resource_group + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.storage_accounts_with_secure_transfer_required_disabled_default_action + enum = local.storage_accounts_with_secure_transfer_required_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.storage_accounts_with_secure_transfer_required_disabled_enabled_actions + enum = local.storage_accounts_with_secure_transfer_required_disabled_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected Storage Account ${param.title} with secure transfer required disabled." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped Storage Account ${param.title} with secure transfer required disabled." + } + success_msg = "" + error_msg = "" + }, + "enable_secure_transfer" = { + label = "Enable secure transfer" + value = "enable_secure_transfer" + style = local.style_alert + pipeline_ref = azure.pipeline.update_storage_account_https_only + pipeline_args = { + account_name = param.name + resource_group = param.resource_group + subscription_id = param.subscription_id + conn = param.conn + https_only = true + } + success_msg = "Enabled secure transfer for Storage Account ${param.title}." + error_msg = "Error enabling secure transfer for Storage Account ${param.title}." + } + } + } + } +} diff --git a/pipelines/storage/storage_accounts_with_table_service_logging_disabled.fp b/pipelines/storage/storage_accounts_with_table_service_logging_disabled.fp new file mode 100644 index 0000000..969da76 --- /dev/null +++ b/pipelines/storage/storage_accounts_with_table_service_logging_disabled.fp @@ -0,0 +1,340 @@ +locals { + storage_accounts_with_table_service_logging_disabled_query = <<-EOQ + with get_access_key as ( + select + distinct on (id) id, + k ->> 'Value' as access_key + from + azure_storage_account, + jsonb_array_elements(access_keys) as k + order by + id + ) + select + distinct concat(sa.id, ' [', sa.resource_group, '/', sa.subscription_id, ']') as title, + sa.id as id, + k.access_key as access_key, + sa.name, + sa.subscription_id, + sa._ctx ->> 'connection_name' as conn + from + azure_storage_account as sa, + get_access_key as k, + azure_subscription as sub + where + sub.subscription_id = sa.subscription_id + and k.id = sa.id + and ( + not table_logging_write + or not table_logging_read + or not table_logging_delete + ) + EOQ + + storage_accounts_with_table_service_logging_disabled_enabled_actions_enum = ["skip", "enable_table_service_logging"] + storage_accounts_with_table_service_logging_disabled_default_action_enum = ["notify", "skip", "enable_table_service_logging"] +} + +variable "storage_accounts_with_table_service_logging_disabled_trigger_enabled" { + type = bool + default = false + description = "If true, the trigger is enabled." + + tags = { + folder = "Advanced/Storage" + } +} + +variable "storage_accounts_with_table_service_logging_disabled_trigger_schedule" { + type = string + default = "15m" + description = "If the trigger is enabled, run it on this schedule." + + tags = { + folder = "Advanced/Storage" + } +} + +variable "storage_accounts_with_table_service_logging_disabled_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/Storage" + } +} + +variable "storage_accounts_with_table_service_logging_disabled_enabled_actions" { + type = list(string) + description = "The list of enabled actions to provide to approvers for selection." + default = ["skip", "enable_table_service_logging"] + + tags = { + folder = "Advanced/Storage" + } +} + +trigger "query" "detect_and_correct_storage_accounts_with_table_service_logging_disabled" { + title = "Detect & correct Storage Accounts with table service logging disabled" + description = "Detect Storage Accounts with table service logging disabled and then enable table service logging." + tags = local.storage_common_tags + + enabled = var.storage_accounts_with_table_service_logging_disabled_trigger_enabled + schedule = var.storage_accounts_with_table_service_logging_disabled_trigger_schedule + database = var.database + sql = local.storage_accounts_with_table_service_logging_disabled_query + + capture "insert" { + pipeline = pipeline.correct_storage_accounts_with_table_service_logging_disabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_storage_accounts_with_table_service_logging_disabled" { + title = "Detect & correct Storage Accounts with table service logging disabled" + description = "Detect Storage Accounts with table service logging disabled and then enable table service logging." + tags = merge(local.storage_common_tags, { recommended = "true" }) + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.storage_accounts_with_table_service_logging_disabled_default_action + enum = local.storage_accounts_with_table_service_logging_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.storage_accounts_with_table_service_logging_disabled_enabled_actions + enum = local.storage_accounts_with_table_service_logging_disabled_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.storage_accounts_with_table_service_logging_disabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_storage_accounts_with_table_service_logging_disabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_storage_accounts_with_table_service_logging_disabled" { + title = "Correct Storage Accounts with table service logging disabled" + description = "Enable table service logging for Storage Accounts with table service logging disabled." + tags = merge(local.storage_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + name = string + subscription_id = string + access_key = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.storage_accounts_with_table_service_logging_disabled_default_action + enum = local.storage_accounts_with_table_service_logging_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.storage_accounts_with_table_service_logging_disabled_enabled_actions + enum = local.storage_accounts_with_table_service_logging_disabled_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} Storage Account(s) with table service logging disabled." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.id => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_storage_account_with_table_service_logging_disabled + args = { + title = each.value.title + name = each.value.name + subscription_id = each.value.subscription_id + access_key = each.value.access_key + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_storage_account_with_table_service_logging_disabled" { + title = "Correct Storage Account with table service logging disabled" + description = "Enable table service logging for a Storage Account with table service logging disabled." + tags = merge(local.storage_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "name" { + type = string + description = "The name of the Storage Account." + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "access_key" { + type = string + description = "The access key of the storage account." + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.storage_accounts_with_table_service_logging_disabled_default_action + enum = local.storage_accounts_with_table_service_logging_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.storage_accounts_with_table_service_logging_disabled_enabled_actions + enum = local.storage_accounts_with_table_service_logging_disabled_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected Storage Account ${param.title} with table service logging disabled." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped Storage Account ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "enable_table_service_logging" = { + label = "Enable table service logging" + value = "enable_table_service_logging" + style = local.style_alert + pipeline_ref = azure.pipeline.update_storage_account_logging + pipeline_args = { + account_name = param.name + subscription_id = param.subscription_id + access_key = param.access_key + conn = param.conn + services = "t" + log = "rwd" + retention = 90 + } + success_msg = "Enabled table service logging for Storage Account ${param.title}." + error_msg = "Error enabling table service logging for Storage Account ${param.title}." + } + } + } + } +} + diff --git a/pipelines/storage/storage_accounts_with_trusted_microsoft_services_disabled.fp b/pipelines/storage/storage_accounts_with_trusted_microsoft_services_disabled.fp new file mode 100644 index 0000000..874d305 --- /dev/null +++ b/pipelines/storage/storage_accounts_with_trusted_microsoft_services_disabled.fp @@ -0,0 +1,321 @@ +locals { + storage_accounts_with_trusted_microsoft_services_disabled_query = <<-EOQ + select + concat(sa.id, ' [', sa.resource_group, '/', sa.subscription_id, ']') as title, + sa.id as id, + sa.name, + sa.resource_group, + sa.subscription_id, + sa._ctx ->> 'connection_name' as conn + from + azure_storage_account as sa, + azure_subscription as sub + where + sub.subscription_id = sa.subscription_id + and network_rule_bypass not like '%AzureServices%'; + EOQ + + storage_accounts_with_trusted_microsoft_services_disabled_enabled_actions_enum = ["skip", "enable_trusted_microsoft_services"] + storage_accounts_with_trusted_microsoft_services_disabled_default_action_enum = ["notify", "skip", "enable_trusted_microsoft_services"] +} + +variable "storage_accounts_with_trusted_microsoft_services_disabled_trigger_enabled" { + type = bool + default = false + description = "If true, the trigger is enabled." + + tags = { + folder = "Advanced/Storage" + } +} + +variable "storage_accounts_with_trusted_microsoft_services_disabled_trigger_schedule" { + type = string + default = "15m" + description = "If the trigger is enabled, run it on this schedule." + + tags = { + folder = "Advanced/Storage" + } +} + +variable "storage_accounts_with_trusted_microsoft_services_disabled_default_action" { + type = string + description = "The default action to use when there are no approvers." + default = "notify" + + tags = { + folder = "Advanced/Storage" + } +} + +variable "storage_accounts_with_trusted_microsoft_services_disabled_enabled_actions" { + type = list(string) + description = "The list of enabled actions to provide to approvers for selection." + default = ["skip", "enable_trusted_microsoft_services"] + + tags = { + folder = "Advanced/Storage" + } +} + +trigger "query" "detect_and_correct_storage_accounts_with_trusted_microsoft_services_disabled" { + title = "Detect & correct Storage Accounts with trusted Microsoft services access disabled" + description = "Detect Storage Accounts with trusted Microsoft services access disabled and then enable trusted Microsoft services." + tags = local.storage_common_tags + + enabled = var.storage_accounts_with_trusted_microsoft_services_disabled_trigger_enabled + schedule = var.storage_accounts_with_trusted_microsoft_services_disabled_trigger_schedule + database = var.database + sql = local.storage_accounts_with_trusted_microsoft_services_disabled_query + + capture "insert" { + pipeline = pipeline.correct_storage_accounts_with_trusted_microsoft_services_disabled + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_storage_accounts_with_trusted_microsoft_services_disabled" { + title = "Detect & correct Storage Accounts with trusted Microsoft services access disabled" + description = "Detect Storage Accounts with trusted Microsoft services access disabled and then enable trusted Microsoft services." + tags = merge(local.storage_common_tags, { recommended = "true" }) + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.storage_accounts_with_trusted_microsoft_services_disabled_default_action + enum = local.storage_accounts_with_trusted_microsoft_services_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.storage_accounts_with_trusted_microsoft_services_disabled_enabled_actions + enum = local.storage_accounts_with_trusted_microsoft_services_disabled_enabled_actions_enum + } + + step "query" "detect" { + database = param.database + sql = local.storage_accounts_with_trusted_microsoft_services_disabled_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_storage_accounts_with_trusted_microsoft_services_disabled + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_storage_accounts_with_trusted_microsoft_services_disabled" { + title = "Correct Storage Accounts with trusted Microsoft services access disabled" + description = "Enable trusted Microsoft services for Storage Accounts with trusted Microsoft services access disabled." + tags = merge(local.storage_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + id = string + title = string + name = string + resource_group = string + subscription_id = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.storage_accounts_with_trusted_microsoft_services_disabled_default_action + enum = local.storage_accounts_with_trusted_microsoft_services_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.storage_accounts_with_trusted_microsoft_services_disabled_enabled_actions + enum = local.storage_accounts_with_trusted_microsoft_services_disabled_enabled_actions_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} Storage Account(s) with trusted Microsoft services access disabled." + } + + step "pipeline" "correct_item" { + for_each = { for row in param.items : row.id => row } + max_concurrency = var.max_concurrency + pipeline = pipeline.correct_one_storage_account_with_trusted_microsoft_services_disabled + args = { + title = each.value.title + name = each.value.name + resource_group = each.value.resource_group + subscription_id = each.value.subscription_id + conn = connection.azure[each.value.conn] + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + default_action = param.default_action + enabled_actions = param.enabled_actions + } + } +} + +pipeline "correct_one_storage_account_with_trusted_microsoft_services_disabled" { + title = "Correct Storage Account with trusted Microsoft services access disabled" + description = "Enable trusted Microsoft services for a Storage Account with trusted Microsoft services access disabled." + tags = merge(local.storage_common_tags, { folder = "Internal" }) + + param "title" { + type = string + description = local.description_title + } + + param "name" { + type = string + description = "The name of the Storage Account." + } + + param "resource_group" { + type = string + description = local.description_resource_group + } + + param "subscription_id" { + type = string + description = local.description_subscription_id + } + + param "conn" { + type = connection.azure + description = local.description_connection + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + param "approvers" { + type = list(notifier) + description = local.description_approvers + default = var.approvers + } + + param "default_action" { + type = string + description = local.description_default_action + default = var.storage_accounts_with_trusted_microsoft_services_disabled_default_action + enum = local.storage_accounts_with_trusted_microsoft_services_disabled_default_action_enum + } + + param "enabled_actions" { + type = list(string) + description = local.description_enabled_actions + default = var.storage_accounts_with_trusted_microsoft_services_disabled_enabled_actions + enum = local.storage_accounts_with_trusted_microsoft_services_disabled_enabled_actions_enum + } + + step "pipeline" "respond" { + pipeline = detect_correct.pipeline.correction_handler + args = { + notifier = param.notifier + notification_level = param.notification_level + approvers = param.approvers + detect_msg = "Detected Storage Account ${param.title} with trusted Microsoft services access disabled." + default_action = param.default_action + enabled_actions = param.enabled_actions + actions = { + "skip" = { + label = "Skip" + value = "skip" + style = local.style_info + pipeline_ref = detect_correct.pipeline.optional_message + pipeline_args = { + notifier = param.notifier + send = param.notification_level == local.level_info + text = "Skipped Storage Account ${param.title}." + } + success_msg = "" + error_msg = "" + }, + "enable_trusted_microsoft_services" = { + label = "Enable trusted microsoft services" + value = "enable_trusted_microsoft_services" + style = local.style_alert + pipeline_ref = azure.pipeline.update_storage_account_bypass_azure_services + pipeline_args = { + account_name = param.name + resource_group = param.resource_group + subscription_id = param.subscription_id + conn = param.conn + } + success_msg = "Enabled trusted Microsoft services access for Storage Account ${param.title}." + error_msg = "Error enabling trusted Microsoft services access for Storage Account ${param.title}." + } + } + } + } +} + diff --git a/pipelines/storage/storage_accounts_without_private_link.fp b/pipelines/storage/storage_accounts_without_private_link.fp new file mode 100644 index 0000000..2dc8e91 --- /dev/null +++ b/pipelines/storage/storage_accounts_without_private_link.fp @@ -0,0 +1,141 @@ +locals { + storage_accounts_without_private_link_query = <<-EOQ + with storage_account_connection as ( + select + distinct a.id + from + azure_storage_account as a, + jsonb_array_elements(private_endpoint_connections) as connection + where + connection -> 'properties' -> 'privateLinkServiceConnectionState' ->> 'status' = 'Approved' + ) + select + concat(id, ' [', subscription_id, '/', resource_group, ']') as title, + id as id, + name, + resource_group, + subscription_id, + _ctx ->> 'connection_name' as conn + from + azure_storage_account + where + id not in (select id from storage_account_connection); + EOQ +} + +variable "storage_accounts_without_private_link_trigger_enabled" { + type = bool + description = "If true, the trigger is enabled." + default = false + + tags = { + folder = "Advanced/Storage" + } +} + +variable "storage_accounts_without_private_link_trigger_schedule" { + type = string + description = "If the trigger is enabled, run it on this schedule." + default = "15m" + + tags = { + folder = "Advanced/Storage" + } +} + +trigger "query" "detect_and_correct_storage_accounts_without_private_link" { + title = "Detect & correct Storage Accounts not using private link" + description = "Detect Storage Accounts not using private link." + tags = local.storage_common_tags + + enabled = var.storage_accounts_without_private_link_trigger_enabled + schedule = var.storage_accounts_without_private_link_trigger_schedule + database = var.database + sql = local.storage_accounts_without_private_link_query + + capture "insert" { + pipeline = pipeline.correct_storage_accounts_without_private_link + args = { + items = self.inserted_rows + } + } +} + +pipeline "detect_and_correct_storage_accounts_without_private_link" { + title = "Detect & correct Storage Accounts not using private link" + description = "Detect Storage Accounts not using private link." + tags = local.storage_common_tags + + param "database" { + type = connection.steampipe + description = local.description_database + default = var.database + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "query" "detect" { + database = param.database + sql = local.storage_accounts_without_private_link_query + } + + step "pipeline" "respond" { + pipeline = pipeline.correct_storage_accounts_without_private_link + args = { + items = step.query.detect.rows + notifier = param.notifier + notification_level = param.notification_level + } + } +} + +pipeline "correct_storage_accounts_without_private_link" { + title = "Correct Storage Accounts not using private link" + description = "Send notifications for Storage Accounts not using private link." + tags = merge(local.storage_common_tags, { folder = "Internal" }) + + param "items" { + type = list(object({ + title = string + conn = string + })) + description = local.description_items + } + + param "notifier" { + type = notifier + description = local.description_notifier + default = var.notifier + } + + param "notification_level" { + type = string + description = local.description_notifier_level + default = var.notification_level + enum = local.notification_level_enum + } + + step "message" "notify_detection_count" { + if = var.notification_level == local.level_info + notifier = param.notifier + text = "Detected ${length(param.items)} Storage Account(s) without private link." + } + + step "message" "notify_items" { + if = var.notification_level == local.level_info + for_each = param.items + notifier = param.notifier + text = "Detected Storage Account ${each.value.title} without private link." + } +} diff --git a/variables.fp b/variables.fp new file mode 100644 index 0000000..7c5aab0 --- /dev/null +++ b/variables.fp @@ -0,0 +1,37 @@ +variable "approvers" { + type = list(notifier) + description = "List of notifiers to be used for obtaining action/approval decisions, when empty list will perform the default response associated with the detection." + default = [notifier.default] +} + +variable "notifier" { + type = notifier + description = "The notifier to use for sending notification messages." + default = notifier.default +} + +variable "notification_level" { + type = string + description = "The verbosity level of notification messages to send." + default = "info" +} + +variable "database" { + type = connection.steampipe + description = "Steampipe database connection string." + default = connection.steampipe.default + + tags = { + folder = "Advanced" + } +} + +variable "max_concurrency" { + type = number + description = "The maximum concurrency to use for responding to detection items." + default = 1 + + tags = { + folder = "Advanced" + } +} From cd9ed97246548a5d376724f9e71cd6879e7f8b38 Mon Sep 17 00:00:00 2001 From: misraved Date: Mon, 16 Dec 2024 16:13:57 +0530 Subject: [PATCH 2/3] Add CHANGELOG for v1.0.0 --- CHANGELOG.md | 5 +++++ pipelines/iam/iam_subscriptions_with_custom_owner_roles.fp | 6 +++--- 2 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..0ccdec3 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +## v1.0.0 [2024-10-23] + +_What's new?_ + +- Added 109 new 'detect and correct' pipelines to identify Azure resources that are non-compliant with common security and compliance checks. These pipelines can also remediate non-compliant automatically or with approval steps. For usage information and a full list of pipelines, please see [Azure Compliance Mod](https://hub.flowpipe.io/mods/turbot/azure_compliance). diff --git a/pipelines/iam/iam_subscriptions_with_custom_owner_roles.fp b/pipelines/iam/iam_subscriptions_with_custom_owner_roles.fp index c53ae2c..3f520c4 100644 --- a/pipelines/iam/iam_subscriptions_with_custom_owner_roles.fp +++ b/pipelines/iam/iam_subscriptions_with_custom_owner_roles.fp @@ -60,7 +60,7 @@ variable "iam_subscriptions_with_custom_owner_roles_enabled_actions" { } trigger "query" "detect_and_correct_iam_subscriptions_with_custom_owner_roles" { - title = "Detect & correct subscriptions with custom owner roles" + title = "Detect & correct Subscriptions with custom owner roles" description = "Detect subscriptions with custom owner roles and then delete custom subscriptions owner roles." tags = local.iam_common_tags @@ -78,7 +78,7 @@ trigger "query" "detect_and_correct_iam_subscriptions_with_custom_owner_roles" { } pipeline "detect_and_correct_iam_subscriptions_with_custom_owner_roles" { - title = "Detect & correct subscriptions with custom owner roles" + title = "Detect & correct Subscriptions with custom owner roles" description = "Detect subscriptions with custom owner roles and then delete custom subscriptions owner roles." tags = merge(local.iam_common_tags, { recommended = "true" }) @@ -140,7 +140,7 @@ pipeline "detect_and_correct_iam_subscriptions_with_custom_owner_roles" { } pipeline "correct_iam_subscriptions_with_custom_owner_roles" { - title = "Correct subscriptions with custom owner roles" + title = "Correct Subscriptions with custom owner roles" description = "Runs corrective action on a collection of subscriptions with custom owner roles." tags = merge(local.iam_common_tags, { folder = "Internal" }) From 5546d94f9a51dad1cc406b024606aa60ba6489e6 Mon Sep 17 00:00:00 2001 From: misraved Date: Mon, 16 Dec 2024 16:20:21 +0530 Subject: [PATCH 3/3] Fix the mod color --- CHANGELOG.md | 2 +- flowpipe.fpvars.example | 8 ++++++++ mod.fp | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 flowpipe.fpvars.example diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ccdec3..b27c4c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## v1.0.0 [2024-10-23] +## v1.0.0 [2024-12-16] _What's new?_ diff --git a/flowpipe.fpvars.example b/flowpipe.fpvars.example new file mode 100644 index 0000000..beed424 --- /dev/null +++ b/flowpipe.fpvars.example @@ -0,0 +1,8 @@ +# Core options +approvers = [notifier.default] +notifier = notifier.default +notification_level = "info" + +# Advanced options +database = connection.steampipe.default +max_concurrency = 1 diff --git a/mod.fp b/mod.fp index 7726277..6e6d6fa 100644 --- a/mod.fp +++ b/mod.fp @@ -1,7 +1,7 @@ mod "azure_compliance" { title = "Azure Compliance" description = "Run pipelines to detect and correct Azure resources that are non-compliant." - color = "#ea4335" + color = "#0089D6" documentation = file("./README.md") icon = "/images/mods/turbot/azure-compliance.svg" categories = ["azure", "compliance", "public cloud", "standard"]