From 0c5bb6fd5bd0f4e4a1532ebc345ee887b43737f3 Mon Sep 17 00:00:00 2001 From: ParthaI Date: Thu, 29 Aug 2024 18:32:43 +0530 Subject: [PATCH 1/2] Add table azure_web_application_firewall_policy Closes #827 --- .../dependencies.txt | 0 .../test-get-expected.json | 10 + .../test-get-query.sql | 3 + .../test-list-expected.json | 10 + .../test-list-query.sql | 3 + .../test-not-found-expected.json | 1 + .../test-not-found-query.sql | 3 + .../test-turbot-expected.json | 10 + .../test-turbot-query.sql | 3 + .../variables.json | 1 + .../variables.tf | 142 ++++++++++++ azure/plugin.go | 1 + ...e_azure_web_application_firewall_policy.go | 213 ++++++++++++++++++ .../azure_web_application_firewall_policy.md | 183 +++++++++++++++ 14 files changed, 583 insertions(+) create mode 100644 azure-test/tests/azure_web_application_firewall_policy/dependencies.txt create mode 100644 azure-test/tests/azure_web_application_firewall_policy/test-get-expected.json create mode 100644 azure-test/tests/azure_web_application_firewall_policy/test-get-query.sql create mode 100644 azure-test/tests/azure_web_application_firewall_policy/test-list-expected.json create mode 100644 azure-test/tests/azure_web_application_firewall_policy/test-list-query.sql create mode 100644 azure-test/tests/azure_web_application_firewall_policy/test-not-found-expected.json create mode 100644 azure-test/tests/azure_web_application_firewall_policy/test-not-found-query.sql create mode 100644 azure-test/tests/azure_web_application_firewall_policy/test-turbot-expected.json create mode 100644 azure-test/tests/azure_web_application_firewall_policy/test-turbot-query.sql create mode 100644 azure-test/tests/azure_web_application_firewall_policy/variables.json create mode 100644 azure-test/tests/azure_web_application_firewall_policy/variables.tf create mode 100644 azure/table_azure_web_application_firewall_policy.go create mode 100644 docs/tables/azure_web_application_firewall_policy.md diff --git a/azure-test/tests/azure_web_application_firewall_policy/dependencies.txt b/azure-test/tests/azure_web_application_firewall_policy/dependencies.txt new file mode 100644 index 00000000..e69de29b diff --git a/azure-test/tests/azure_web_application_firewall_policy/test-get-expected.json b/azure-test/tests/azure_web_application_firewall_policy/test-get-expected.json new file mode 100644 index 00000000..556d42f8 --- /dev/null +++ b/azure-test/tests/azure_web_application_firewall_policy/test-get-expected.json @@ -0,0 +1,10 @@ +[ + { + "id": "{{ output.resource_id.value }}", + "name": "{{ resourceName }}", + "region": "{{ output.region.value }}", + "resource_group": "{{ resourceName }}", + "subscription_id": "{{ output.subscription_id.value }}", + "type": "Microsoft.Network/ApplicationGatewayWebApplicationFirewallPolicies" + } +] diff --git a/azure-test/tests/azure_web_application_firewall_policy/test-get-query.sql b/azure-test/tests/azure_web_application_firewall_policy/test-get-query.sql new file mode 100644 index 00000000..e042327c --- /dev/null +++ b/azure-test/tests/azure_web_application_firewall_policy/test-get-query.sql @@ -0,0 +1,3 @@ +select name, id, type, region, resource_group, subscription_id +from azure.azure_web_application_firewall_policy +where name = '{{ resourceName }}' and resource_group = '{{ resourceName }}'; diff --git a/azure-test/tests/azure_web_application_firewall_policy/test-list-expected.json b/azure-test/tests/azure_web_application_firewall_policy/test-list-expected.json new file mode 100644 index 00000000..556d42f8 --- /dev/null +++ b/azure-test/tests/azure_web_application_firewall_policy/test-list-expected.json @@ -0,0 +1,10 @@ +[ + { + "id": "{{ output.resource_id.value }}", + "name": "{{ resourceName }}", + "region": "{{ output.region.value }}", + "resource_group": "{{ resourceName }}", + "subscription_id": "{{ output.subscription_id.value }}", + "type": "Microsoft.Network/ApplicationGatewayWebApplicationFirewallPolicies" + } +] diff --git a/azure-test/tests/azure_web_application_firewall_policy/test-list-query.sql b/azure-test/tests/azure_web_application_firewall_policy/test-list-query.sql new file mode 100644 index 00000000..d00d1a31 --- /dev/null +++ b/azure-test/tests/azure_web_application_firewall_policy/test-list-query.sql @@ -0,0 +1,3 @@ +select name, id, type, region, resource_group, subscription_id +from azure.azure_web_application_firewall_policy +where id = '{{ output.resource_id.value }}'; diff --git a/azure-test/tests/azure_web_application_firewall_policy/test-not-found-expected.json b/azure-test/tests/azure_web_application_firewall_policy/test-not-found-expected.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/azure-test/tests/azure_web_application_firewall_policy/test-not-found-expected.json @@ -0,0 +1 @@ +[] diff --git a/azure-test/tests/azure_web_application_firewall_policy/test-not-found-query.sql b/azure-test/tests/azure_web_application_firewall_policy/test-not-found-query.sql new file mode 100644 index 00000000..e9fa3a0a --- /dev/null +++ b/azure-test/tests/azure_web_application_firewall_policy/test-not-found-query.sql @@ -0,0 +1,3 @@ +select name, id, type, region +from azure.azure_web_application_firewall_policy +where name = 'dummy-test{{ resourceName }}' and resource_group = '{{ resourceName }}'; diff --git a/azure-test/tests/azure_web_application_firewall_policy/test-turbot-expected.json b/azure-test/tests/azure_web_application_firewall_policy/test-turbot-expected.json new file mode 100644 index 00000000..02cd3c76 --- /dev/null +++ b/azure-test/tests/azure_web_application_firewall_policy/test-turbot-expected.json @@ -0,0 +1,10 @@ +[ + { + "akas": [ + "{{ output.resource_aka.value }}", + "{{ output.resource_aka_lower.value }}" + ], + "name": "{{ resourceName }}", + "title": "{{ resourceName }}" + } +] diff --git a/azure-test/tests/azure_web_application_firewall_policy/test-turbot-query.sql b/azure-test/tests/azure_web_application_firewall_policy/test-turbot-query.sql new file mode 100644 index 00000000..d79d01a8 --- /dev/null +++ b/azure-test/tests/azure_web_application_firewall_policy/test-turbot-query.sql @@ -0,0 +1,3 @@ +select name, akas, title +from azure.azure_web_application_firewall_policy +where name = '{{ resourceName }}' and resource_group = '{{ resourceName }}'; diff --git a/azure-test/tests/azure_web_application_firewall_policy/variables.json b/azure-test/tests/azure_web_application_firewall_policy/variables.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/azure-test/tests/azure_web_application_firewall_policy/variables.json @@ -0,0 +1 @@ +{} diff --git a/azure-test/tests/azure_web_application_firewall_policy/variables.tf b/azure-test/tests/azure_web_application_firewall_policy/variables.tf new file mode 100644 index 00000000..066c65fd --- /dev/null +++ b/azure-test/tests/azure_web_application_firewall_policy/variables.tf @@ -0,0 +1,142 @@ +variable "resource_name" { + type = string + default = "turbot-test-20200125-create-update" + description = "Name of the resource used throughout the test." +} + +variable "azure_environment" { + type = string + default = "public" + description = "Azure environment used for the test." +} + +variable "azure_subscription" { + type = string + default = "3510ae4d-530b-497d-8f30-53b9616fc6c1" + description = "Azure environment used for the test." +} + +terraform { + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "=2.75.0" + } + } +} + +provider "azurerm" { + # Cannot be passed as a variable + environment = var.azure_environment + subscription_id = var.azure_subscription + features {} +} + +resource "azurerm_resource_group" "named_test_resource" { + name = var.resource_name + location = "West Europe" +} + +resource "azurerm_web_application_firewall_policy" "named_test_resource" { + name = var.resource_name + resource_group_name = azurerm_resource_group.named_test_resource.name + location = azurerm_resource_group.named_test_resource.location + + custom_rules { + name = "Rule1" + priority = 1 + rule_type = "MatchRule" + + match_conditions { + match_variables { + variable_name = "RemoteAddr" + } + + operator = "IPMatch" + negation_condition = false + match_values = ["192.168.1.0/24", "10.0.0.0/24"] + } + + action = "Block" + } + + custom_rules { + name = "Rule2" + priority = 2 + rule_type = "MatchRule" + + match_conditions { + match_variables { + variable_name = "RemoteAddr" + } + + operator = "IPMatch" + negation_condition = false + match_values = ["192.168.1.0/24"] + } + + match_conditions { + match_variables { + variable_name = "RequestHeaders" + selector = "UserAgent" + } + + operator = "Contains" + negation_condition = false + match_values = ["Windows"] + } + + action = "Block" + } + + policy_settings { + enabled = true + mode = "Prevention" + request_body_check = true + file_upload_limit_in_mb = 100 + max_request_body_size_in_kb = 128 + } + + managed_rules { + exclusion { + match_variable = "RequestHeaderNames" + selector = "x-company-secret-header" + selector_match_operator = "Equals" + } + exclusion { + match_variable = "RequestCookieNames" + selector = "too-tasty" + selector_match_operator = "EndsWith" + } + + managed_rule_set { + type = "OWASP" + version = "3.2" + } + } +} + +output "region" { + value = azurerm_resource_group.named_test_resource.location +} + +output "resource_aka" { + depends_on = [azurerm_web_application_firewall_policy.named_test_resource] + value = "azure://${azurerm_web_application_firewall_policy.named_test_resource.id}" +} + +output "resource_aka_lower" { + value = "azure://${lower(azurerm_web_application_firewall_policy.named_test_resource.id)}" +} + +output "resource_name" { + value = var.resource_name +} + +output "resource_id" { + value = azurerm_web_application_firewall_policy.named_test_resource.id +} + +output "subscription_id" { + value = var.azure_subscription +} diff --git a/azure/plugin.go b/azure/plugin.go index d1624245..ebae004b 100644 --- a/azure/plugin.go +++ b/azure/plugin.go @@ -199,6 +199,7 @@ func Plugin(ctx context.Context) *plugin.Plugin { "azure_tenant": tableAzureTenant(ctx), "azure_virtual_network": tableAzureVirtualNetwork(ctx), "azure_virtual_network_gateway": tableAzureVirtualNetworkGateway(ctx), + "azure_web_application_firewall_policy": tableAzureWebApplicationFirewallPolicy(ctx), }, } diff --git a/azure/table_azure_web_application_firewall_policy.go b/azure/table_azure_web_application_firewall_policy.go new file mode 100644 index 00000000..d59a4264 --- /dev/null +++ b/azure/table_azure_web_application_firewall_policy.go @@ -0,0 +1,213 @@ +package azure + +import ( + "context" + + "github.com/Azure/azure-sdk-for-go/profiles/latest/network/mgmt/network" + "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform" + + "github.com/turbot/steampipe-plugin-sdk/v5/plugin" +) + +//// TABLE DEFINITION //// + +func tableAzureWebApplicationFirewallPolicy(_ context.Context) *plugin.Table { + return &plugin.Table{ + Name: "azure_web_application_firewall_policy", + Description: "Azure Web Application Firewall Policy", + Get: &plugin.GetConfig{ + KeyColumns: plugin.AllColumns([]string{"name", "resource_group"}), + Hydrate: getWebApplicationFirewallPolicy, + IgnoreConfig: &plugin.IgnoreConfig{ + ShouldIgnoreErrorFunc: isNotFoundError([]string{"ResourceGroupNotFound", "ResourceNotFound", "404"}), + }, + }, + List: &plugin.ListConfig{ + Hydrate: listWebApplicationFirewallPolicies, + }, + Columns: azureColumns([]*plugin.Column{ + { + Name: "name", + Type: proto.ColumnType_STRING, + Description: "Resource name.", + }, + { + Name: "id", + Description: "Resource ID.", + Type: proto.ColumnType_STRING, + Transform: transform.FromGo(), + }, + { + Name: "etag", + Description: "A unique read-only string that changes whenever the resource is updated.", + Type: proto.ColumnType_STRING, + }, + { + Name: "type", + Description: "Resource type.", + Type: proto.ColumnType_STRING, + }, + { + Name: "provisioning_state", + Description: "The provisioning state of the web application firewall policy resource. Possible values include: 'ProvisioningStateSucceeded', 'ProvisioningStateUpdating', 'ProvisioningStateDeleting', 'ProvisioningStateFailed'.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("WebApplicationFirewallPolicyPropertiesFormat.ProvisioningState"), + }, + { + Name: "resource_state", + Description: "Resource status of the policy. Possible values include: 'WebApplicationFirewallPolicyResourceStateCreating', 'WebApplicationFirewallPolicyResourceStateEnabling', 'WebApplicationFirewallPolicyResourceStateEnabled', 'WebApplicationFirewallPolicyResourceStateDisabling', 'WebApplicationFirewallPolicyResourceStateDisabled', 'WebApplicationFirewallPolicyResourceStateDeleting'.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("WebApplicationFirewallPolicyPropertiesFormat.ResourceState"), + }, + { + Name: "policy_settings", + Description: "The PolicySettings for policy.", + Type: proto.ColumnType_JSON, + Transform: transform.FromField("WebApplicationFirewallPolicyPropertiesFormat.PolicySettings"), + }, + { + Name: "custom_rules", + Description: "The custom rules inside the policy.", + Type: proto.ColumnType_JSON, + Transform: transform.FromField("WebApplicationFirewallPolicyPropertiesFormat.CustomRules"), + }, + { + Name: "application_gateways", + Description: "A collection of references to application gateways.", + Type: proto.ColumnType_JSON, + Transform: transform.FromField("WebApplicationFirewallPolicyPropertiesFormat.ApplicationGateways"), + }, + { + Name: "managed_rules", + Description: "Describes the managedRules structure.", + Type: proto.ColumnType_JSON, + Transform: transform.FromField("WebApplicationFirewallPolicyPropertiesFormat.ManagedRules"), + }, + { + Name: "http_listeners", + Description: "A collection of references to application gateway http listeners.", + Type: proto.ColumnType_JSON, + Transform: transform.FromField("WebApplicationFirewallPolicyPropertiesFormat.HTTPListeners"), + }, + { + Name: "path_based_rules", + Description: "A collection of references to application gateway path rules.", + Type: proto.ColumnType_JSON, + Transform: transform.FromField("WebApplicationFirewallPolicyPropertiesFormat.PathBasedRules"), + }, + + // Steampipe standard columns + { + Name: "title", + Description: ColumnDescriptionTitle, + Type: proto.ColumnType_STRING, + Transform: transform.FromField("Name"), + }, + { + Name: "tags", + Description: ColumnDescriptionTags, + Type: proto.ColumnType_JSON, + }, + { + Name: "akas", + Description: ColumnDescriptionAkas, + Type: proto.ColumnType_JSON, + Transform: transform.FromField("ID").Transform(idToAkas), + }, + + // Azure standard columns + { + Name: "region", + Description: ColumnDescriptionRegion, + Type: proto.ColumnType_STRING, + Transform: transform.FromField("Location").Transform(toLower), + }, + { + Name: "resource_group", + Description: ColumnDescriptionResourceGroup, + Type: proto.ColumnType_STRING, + Transform: transform.FromField("ID").Transform(extractResourceGroupFromID), + }, + }), + } +} + +//// FETCH FUNCTIONS //// + +func listWebApplicationFirewallPolicies(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + session, err := GetNewSession(ctx, d, "MANAGEMENT") + if err != nil { + plugin.Logger(ctx).Error("azure_web_application_firewall_policy.listWebApplicationFirewallPolicies", "session_error", err) + return nil, err + } + subscriptionID := session.SubscriptionID + networkClient := network.NewWebApplicationFirewallPoliciesClientWithBaseURI(session.ResourceManagerEndpoint, subscriptionID) + networkClient.Authorizer = session.Authorizer + + result, err := networkClient.ListAll(ctx) + if err != nil { + plugin.Logger(ctx).Error("azure_web_application_firewall_policy.listWebApplicationFirewallPolicies", "api_error", err) + return nil, err + } + for _, firewallPolicy := range result.Values() { + d.StreamListItem(ctx, firewallPolicy) + // Check if context has been cancelled or if the limit has been hit (if specified) + // if there is a limit, it will return the number of rows required to reach this limit + if d.RowsRemaining(ctx) == 0 { + return nil, nil + } + } + + for result.NotDone() { + err = result.NextWithContext(ctx) + if err != nil { + plugin.Logger(ctx).Error("azure_web_application_firewall_policy.listWebApplicationFirewallPolicies", "paging_error", err) + return nil, err + } + + for _, firewallPolicy := range result.Values() { + d.StreamListItem(ctx, firewallPolicy) + // Check if context has been cancelled or if the limit has been hit (if specified) + // if there is a limit, it will return the number of rows required to reach this limit + if d.RowsRemaining(ctx) == 0 { + return nil, nil + } + } + + } + + return nil, err +} + +//// HYDRATE FUNCTIONS //// + +func getWebApplicationFirewallPolicy(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + + name := d.EqualsQuals["name"].GetStringValue() + resourceGroup := d.EqualsQuals["resource_group"].GetStringValue() + + session, err := GetNewSession(ctx, d, "MANAGEMENT") + if err != nil { + plugin.Logger(ctx).Error("azure_web_application_firewall_policy.getWebApplicationFirewallPolicy", "session_error", err) + return nil, err + } + subscriptionID := session.SubscriptionID + + networkClient := network.NewWebApplicationFirewallPoliciesClientWithBaseURI(session.ResourceManagerEndpoint, subscriptionID) + networkClient.Authorizer = session.Authorizer + + op, err := networkClient.Get(ctx, resourceGroup, name) + if err != nil { + plugin.Logger(ctx).Error("azure_web_application_firewall_policy.getWebApplicationFirewallPolicy", "api_error", err) + return nil, err + } + + // In some cases resource does not give any notFound error + // instead of notFound error, it returns empty data + if op.ID != nil { + return op, nil + } + + return nil, nil +} diff --git a/docs/tables/azure_web_application_firewall_policy.md b/docs/tables/azure_web_application_firewall_policy.md new file mode 100644 index 00000000..31fc47c4 --- /dev/null +++ b/docs/tables/azure_web_application_firewall_policy.md @@ -0,0 +1,183 @@ +--- +title: "Steampipe Table: azure_web_application_firewall_policy - Query Azure Web Application Firewall Policies using SQL" +description: "Allows users to query Azure Web Application Firewall (WAF) policies, providing detailed information on policy configurations, rules, and associated resources." +--- + +# Table: azure_web_application_firewall_policy - Query Azure Web Application Firewall Policies using SQL + +Azure Web Application Firewall (WAF) is a service that helps protect your web applications by filtering and monitoring HTTP traffic between a web application and the Internet. The `azure_web_application_firewall_policy` table in Steampipe allows you to query information about WAF policies in Azure, including their custom and managed rules, associated application gateways, HTTP listeners, and more. + +## Table Usage Guide + +The `azure_web_application_firewall_policy` table enables cloud administrators and security engineers to gather detailed insights into their WAF policies. You can query various aspects of the policies, such as their custom rules, managed rules, provisioning state, and associated application gateways. This table is particularly useful for monitoring the security of web applications, managing WAF policy configurations, and ensuring that your web applications are protected against threats. + +## Examples + +### Basic info +Retrieve basic information about your Azure WAF policies, including their name, resource group, and region. + +```sql+postgres +select + name, + id, + resource_group, + region, + provisioning_state +from + azure_web_application_firewall_policy; +``` + +```sql+sqlite +select + name, + id, + resource_group, + region, + provisioning_state +from + azure_web_application_firewall_policy; +``` + +### List policies with custom rules +Fetch WAF policies that include custom rules, which can be useful for identifying policies with specific, user-defined security measures. + +```sql+postgres +select + name, + custom_rules +from + azure_web_application_firewall_policy +where + custom_rules is not null; +``` + +```sql+sqlite +select + name, + custom_rules +from + azure_web_application_firewall_policy +where + json_extract(custom_rules, '$[0]') is not null; +``` + +### List policies by provisioning state +Identify WAF policies based on their provisioning state, such as those that are currently updating or have failed. + +```sql+postgres +select + name, + provisioning_state, + region +from + azure_web_application_firewall_policy +where + provisioning_state = 'ProvisioningStateUpdating'; +``` + +```sql+sqlite +select + name, + provisioning_state, + region +from + azure_web_application_firewall_policy +where + provisioning_state = 'ProvisioningStateUpdating'; +``` + +### Get managed rules for each policy +Retrieve the managed rules associated with each WAF policy to understand the built-in protections that are applied to your web applications. + +```sql+postgres +select + name, + managed_rules -> 'Exclusions' as exclusions, + managed_rules -> 'ManagedRuleSets' as managed_rule_sets +from + azure_web_application_firewall_policy; +``` + +```sql+sqlite +select + name, + json_extract(managed_rules, '$.Exclusions') as exclusions, + json_extract(managed_rules, '$.ManagedRuleSets') as managed_rule_sets +from + azure_web_application_firewall_policy; +``` + +### List policies with associated application gateways +Identify WAF policies that are linked to specific application gateways, which can help in managing and securing web traffic. + +```sql+postgres +select + name, + application_gateways +from + azure_web_application_firewall_policy +where + application_gateways is not null; +``` + +```sql+sqlite +select + name, + application_gateways +from + azure_web_application_firewall_policy +where + json_extract(application_gateways, '$[0]') is not null; +``` + +### List policies with HTTP listeners +Fetch WAF policies that are associated with specific HTTP listeners, which can be important for understanding how traffic is being monitored and filtered. + +```sql+postgres +select + name, + http_listeners +from + azure_web_application_firewall_policy +where + http_listeners is not null; +``` + +```sql+sqlite +select + name, + http_listeners +from + azure_web_application_firewall_policy +where + json_extract(http_listeners, '$[0]') is not null; +``` + +### Get application gateway details that is associated with the firewall policy +Get application gateway associated with WAF policies and configurations + +```sql+postgres +select + a.name as application_name, + a.provisioning_state as application_provisioning_state, + a.enable_fips, + a.autoscale_configuration, + p.name as policy_name, + p.policy_settings +from + azure_application_gateway as a + join azure_web_application_firewall_policy as p on (a.firewall_policy ->> 'id') = p.id; +``` + +```sql+sqlite +select + a.name as application_name, + a.provisioning_state as application_provisioning_state, + a.enable_fips, + a.autoscale_configuration, + p.name as policy_name, + p.policy_settings +from + azure_application_gateway as a + join azure_web_application_firewall_policy as p on json_extract(a.firewall_policy, '$.id') = p.id; +``` \ No newline at end of file From 37e94f16bbd937fd60a05f4038fe22fbeb153506 Mon Sep 17 00:00:00 2001 From: Ved misra <47312748+misraved@users.noreply.github.com> Date: Mon, 2 Sep 2024 11:21:41 +0530 Subject: [PATCH 2/2] Update azure_web_application_firewall_policy.md --- docs/tables/azure_web_application_firewall_policy.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tables/azure_web_application_firewall_policy.md b/docs/tables/azure_web_application_firewall_policy.md index 31fc47c4..10ced275 100644 --- a/docs/tables/azure_web_application_firewall_policy.md +++ b/docs/tables/azure_web_application_firewall_policy.md @@ -153,7 +153,7 @@ where json_extract(http_listeners, '$[0]') is not null; ``` -### Get application gateway details that is associated with the firewall policy +### Get application gateway details that are associated with the firewall policy Get application gateway associated with WAF policies and configurations ```sql+postgres @@ -180,4 +180,4 @@ select from azure_application_gateway as a join azure_web_application_firewall_policy as p on json_extract(a.firewall_policy, '$.id') = p.id; -``` \ No newline at end of file +```