From 3113604821b137df5a723893f4a17ec63f6a3ea1 Mon Sep 17 00:00:00 2001 From: Jason Zhang Date: Mon, 12 May 2025 20:45:58 +0800 Subject: [PATCH] add waf --- .../c7n_huaweicloud/c7n_huaweicloud/client.py | 12 ++ .../c7n_huaweicloud/c7n_huaweicloud/query.py | 35 ++++ .../c7n_huaweicloud/resources/resource_map.py | 2 + .../c7n_huaweicloud/resources/waf.py | 183 ++++++++++++++++++ .../waf_log_config_enabled_filter_false | 52 +++++ .../waf_log_config_enabled_filter_true | 52 +++++ .../tests/data/flights/waf_log_config_query | 52 +++++ .../flights/waf_log_config_update_disable | 104 ++++++++++ .../data/flights/waf_log_config_update_enable | 104 ++++++++++ .../tests/data/flights/waf_policy_query | 103 ++++++++++ tools/c7n_huaweicloud/tests/test_waf.py | 151 +++++++++++++++ 11 files changed, 850 insertions(+) create mode 100644 tools/c7n_huaweicloud/c7n_huaweicloud/resources/waf.py create mode 100644 tools/c7n_huaweicloud/tests/data/flights/waf_log_config_enabled_filter_false create mode 100644 tools/c7n_huaweicloud/tests/data/flights/waf_log_config_enabled_filter_true create mode 100644 tools/c7n_huaweicloud/tests/data/flights/waf_log_config_query create mode 100644 tools/c7n_huaweicloud/tests/data/flights/waf_log_config_update_disable create mode 100644 tools/c7n_huaweicloud/tests/data/flights/waf_log_config_update_enable create mode 100644 tools/c7n_huaweicloud/tests/data/flights/waf_policy_query create mode 100644 tools/c7n_huaweicloud/tests/test_waf.py diff --git a/tools/c7n_huaweicloud/c7n_huaweicloud/client.py b/tools/c7n_huaweicloud/c7n_huaweicloud/client.py index 4d088cc33..9519fd2bd 100644 --- a/tools/c7n_huaweicloud/c7n_huaweicloud/client.py +++ b/tools/c7n_huaweicloud/c7n_huaweicloud/client.py @@ -105,6 +105,8 @@ from huaweicloudsdkram.v1.region.ram_region import RamRegion from huaweicloudsdkcc.v3 import CcClient, ListCentralNetworksRequest from huaweicloudsdkcc.v3.region.cc_region import CcRegion +from huaweicloudsdkwaf.v1 import WafClient, ListPolicyRequest, ShowLtsInfoConfigRequest +from huaweicloudsdkwaf.v1.region.waf_region import WafRegion log = logging.getLogger("custodian.huaweicloud.client") @@ -424,6 +426,12 @@ def client(self, service): BmsClient.new_builder() .with_credentials(credentials) .with_region(BmsRegion.value_of(self.region)) + ) + elif service in ["waf", "waf-policy", "waf-log-config"]: + client = ( + WafClient.new_builder() + .with_credentials(credentials) + .with_region(WafRegion.value_of(self.region)) .build() ) @@ -538,5 +546,9 @@ def request(self, service): request = ListInstancesRequest() elif service == "bms": request = ListBareMetalServerDetailsRequest() + elif service == 'waf-policy': + request = ListPolicyRequest() + elif service == 'waf-log-config': + request = ShowLtsInfoConfigRequest() return request diff --git a/tools/c7n_huaweicloud/c7n_huaweicloud/query.py b/tools/c7n_huaweicloud/c7n_huaweicloud/query.py index d36e3bbe7..0fd93658b 100644 --- a/tools/c7n_huaweicloud/c7n_huaweicloud/query.py +++ b/tools/c7n_huaweicloud/c7n_huaweicloud/query.py @@ -62,6 +62,8 @@ def filter(self, resource_manager, **params): resources = self._non_pagination(m, enum_op, path) elif pagination == "page": resources = self._pagination_limit_page(m, enum_op, path) + elif pagination == "waf": + resources = self._pagination_waf(m, enum_op, path) else: log.exception(f"Unsupported pagination type: {pagination}") sys.exit(1) @@ -310,6 +312,39 @@ def _pagination_ims(self, m, enum_op, path): data["tag_resource_type"] = m.tag_resource_type resources.extend(res) return resources + + def _pagination_waf(self, m, enum_op, path): + session = local_session(self.session_factory) + client = session.client(m.service) + + page = 1 + resources = [] + while 1: + request = session.request(m.service) + request.page = page + response = self._invoke_client_enum(client, enum_op, request) + res = jmespath.search( + path, + eval( + str(response) + .replace("null", "None") + .replace("false", "False") + .replace("true", "True") + ), + ) + + if not res: + return resources + + # replace id with the specified one + for data in res: + data["id"] = data[m.id] + data["tag_resource_type"] = m.tag_resource_type + + resources = resources + res + + page += 1 + return resources def _get_obs_account_id(self, response, manager, resources): if manager.service == "obs": diff --git a/tools/c7n_huaweicloud/c7n_huaweicloud/resources/resource_map.py b/tools/c7n_huaweicloud/c7n_huaweicloud/resources/resource_map.py index ce3867e70..feb817155 100644 --- a/tools/c7n_huaweicloud/c7n_huaweicloud/resources/resource_map.py +++ b/tools/c7n_huaweicloud/c7n_huaweicloud/resources/resource_map.py @@ -46,4 +46,6 @@ "huaweicloud.kafka": "c7n_huaweicloud.resources.kafka.Kafka", "huaweicloud.cc-cloud-connection": "c7n_huaweicloud.resources.cc.CloudConnection", "huaweicloud.bms": "c7n_huaweicloud.resources.bms.Bms", + "huaweicloud.waf-policy": "c7n_huaweicloud.resources.waf.WafPolicy", + "huaweicloud.waf-log-config": "c7n_huaweicloud.resources.waf.WafLogConfig", } diff --git a/tools/c7n_huaweicloud/c7n_huaweicloud/resources/waf.py b/tools/c7n_huaweicloud/c7n_huaweicloud/resources/waf.py new file mode 100644 index 000000000..dff4531c0 --- /dev/null +++ b/tools/c7n_huaweicloud/c7n_huaweicloud/resources/waf.py @@ -0,0 +1,183 @@ +# Copyright The Cloud Custodian Authors. +# SPDX-License-Identifier: Apache-2.0 + +import logging +from c7n.utils import type_schema +from c7n.filters.core import ValueFilter + +from huaweicloudsdkcore.exceptions import exceptions +from huaweicloudsdkwaf.v1.model.update_lts_info_config_request import UpdateLtsInfoConfigRequest +from huaweicloudsdkwaf.v1.model.update_lts_info_config_request_body import UpdateLtsInfoConfigRequestBody +from huaweicloudsdkwaf.v1.model.lts_id_info import LtsIdInfo + +from c7n_huaweicloud.actions.base import HuaweiCloudBaseAction +from c7n_huaweicloud.provider import resources +from c7n_huaweicloud.query import QueryResourceManager, TypeInfo + +log = logging.getLogger('custodian.huaweicloud.resources.waf') + + +@resources.register('waf-policy') +class WafPolicy(QueryResourceManager): + """Huawei Cloud Web Application Firewall (WAF) Policy Resource + + :example: + + .. code-block:: yaml + + policies: + - name: waf-policy-list + resource: huaweicloud.waf-policy + """ + + class resource_type(TypeInfo): + """Define WAF policy resource metadata and type information""" + service = 'waf-policy' # Specify the corresponding Huawei Cloud service name + # Specify the API operation, result list key name, and pagination parameters for enumerating resources + # 'list_policy' is the API method name + # 'items' is the field name containing the instance list in the response + # None indicates no pagination is used + enum_spec = ('list_policy', 'items', "waf") + id = 'id' # Specify the resource unique identifier field name + name = 'name' # Specify the resource name field + date = 'timestamp' # Specify the resource creation time field + arn_type = 'waf-policy' # Resource ARN type + tag_resource_type = None # Tag not supported + + +@resources.register('waf-log-config') +class WafLogConfig(QueryResourceManager): + """Huawei Cloud Web Application Firewall (WAF) Log Configuration Resource + + :example: + + .. code-block:: yaml + + policies: + - name: waf-log-config-list + resource: huaweicloud.waf-log-config + """ + + class resource_type(TypeInfo): + """Define WAF log configuration resource metadata and type information""" + service = 'waf-log-config' # Specify the corresponding Huawei Cloud service name + # Specify the API operation, result list key name, and pagination parameters for enumerating resources + # 'show_lts_info_config' is the API method name + # 'lts_info' is the field name containing the instance info in the response + # None indicates no pagination is used + enum_spec = ('show_lts_info_config', "[ @ ]", None) + id = 'id' # Specify the resource unique identifier field name + name = 'id' # Specify the resource name field + arn_type = 'waf-log-config' # Resource ARN type + tag_resource_type = None # Tag not supported + + +@WafLogConfig.filter_registry.register('enabled') +class LogEnabledFilter(ValueFilter): + """Filter by WAF log configuration enabled status + + :example: + + .. code-block:: yaml + + policies: + - name: waf-logs-disabled + resource: huaweicloud.waf-log-config + filters: + - type: enabled + value: false # Not enabled + """ + schema = type_schema('enabled', rinherit=ValueFilter.schema) + + def __init__(self, data, manager=None): + """Initialize the filter + + :param data: Filter configuration data + :param manager: Resource manager + """ + super(LogEnabledFilter, self).__init__(data, manager) + self.data['key'] = 'enabled' + + +@WafLogConfig.action_registry.register('update') +class UpdateLogConfig(HuaweiCloudBaseAction): + """Update WAF log configuration + + This operation allows enabling/disabling WAF log configuration and setting log group and log stream information. + + :example: + + .. code-block:: yaml + + policies: + - name: enable-waf-logs + resource: huaweicloud.waf-log-config + filters: + - type: enabled + value: false # Not enabled + actions: + - type: update + enabled: true # Enabled + lts_id_info: + lts_group_id: "4bcff74d-f649-41c8-8325-1b0a264ff683" + lts_access_stream_id: "0a7ef713-cc3e-418d-abda-85df04db1a3c" + lts_attack_stream_id: "f4fa07f6-277b-4e4a-a257-26508ece81e6" + """ + schema = type_schema( + 'update', + enabled={'type': 'boolean'}, + lts_id_info={ + 'type': 'object', + 'properties': { + 'lts_group_id': {'type': 'string'}, + 'lts_access_stream_id': {'type': 'string'}, + 'lts_attack_stream_id': {'type': 'string'} + }, + 'additionalProperties': False + } + ) + + def perform_action(self, resource): + """Perform action on a single resource + + :param resource: Resource to perform action on + :return: API response + """ + client = self.manager.get_client() + resource_id = resource['id'] + + # Construct log information object + lts_id_info_data = self.data.get('lts_id_info') + lts_id_info = None + + if lts_id_info_data: + lts_id_info = LtsIdInfo( + lts_group_id=lts_id_info_data.get('lts_group_id'), + lts_access_stream_id=lts_id_info_data.get('lts_access_stream_id'), + lts_attack_stream_id=lts_id_info_data.get('lts_attack_stream_id') + ) + + # Construct request body + request_body = UpdateLtsInfoConfigRequestBody( + enabled=self.data.get('enabled'), + lts_id_info=lts_id_info + ) + + # Construct request + request = UpdateLtsInfoConfigRequest( + ltsconfig_id=resource_id, + body=request_body + ) + + # Get enterprise project ID from resource data + if 'enterprise_project_id' in resource: + request.enterprise_project_id = resource['enterprise_project_id'] + + try: + # Call API to update log configuration + response = client.update_lts_info_config(request) + log.info(f"Updated WAF log configuration: {resource_id}") + return response + except exceptions.ClientRequestException as e: + log.error(f"Failed to update WAF log configuration: {e.status_code}, {e.request_id}, {e.error_code}, {e.error_msg}") + raise diff --git a/tools/c7n_huaweicloud/tests/data/flights/waf_log_config_enabled_filter_false b/tools/c7n_huaweicloud/tests/data/flights/waf_log_config_enabled_filter_false new file mode 100644 index 000000000..c22eb7c79 --- /dev/null +++ b/tools/c7n_huaweicloud/tests/data/flights/waf_log_config_enabled_filter_false @@ -0,0 +1,52 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, zstd + Connection: + - keep-alive + Content-Type: + - application/json + Host: + - waf.ap-southeast-1.myhuaweicloud.com + User-Agent: + - huaweicloud-usdk-python/3.0 + X-Project-Id: + - ap-southeat-1 + X-Sdk-Date: + - 20250512T114219Z + method: GET + uri: https://waf.ap-southeast-1.myhuaweicloud.com/v1/ap-southeat-1/waf/config/lts + response: + body: + string: '{"id": "log-config-12345", "enabled": false, "lts_id_info": {"lts_group_id": "group-id-12345", "lts_access_stream_id": "access-stream-id-12345", "lts_attack_stream_id": "attack-stream-id-12345"}, "created_at": 1619760096000, "updated_at": 1619760096000}' + headers: + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Mon, 12 May 2025 11:42:19 GMT + Server: + - api-gateway + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Request-Id: + - 193cbe0734c19b58215a6d4ae57e10f3 + X-XSS-Protection: + - 1; mode=block; + status: + code: 200 + message: success +version: 1 diff --git a/tools/c7n_huaweicloud/tests/data/flights/waf_log_config_enabled_filter_true b/tools/c7n_huaweicloud/tests/data/flights/waf_log_config_enabled_filter_true new file mode 100644 index 000000000..cd7fbfa56 --- /dev/null +++ b/tools/c7n_huaweicloud/tests/data/flights/waf_log_config_enabled_filter_true @@ -0,0 +1,52 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, zstd + Connection: + - keep-alive + Content-Type: + - application/json + Host: + - waf.ap-southeast-1.myhuaweicloud.com + User-Agent: + - huaweicloud-usdk-python/3.0 + X-Project-Id: + - ap-southeat-1 + X-Sdk-Date: + - 20250512T114219Z + method: GET + uri: https://waf.ap-southeast-1.myhuaweicloud.com/v1/ap-southeat-1/waf/config/lts + response: + body: + string: '{"id": "log-config-12345", "enabled": true, "lts_id_info": {"lts_group_id": "group-id-12345", "lts_access_stream_id": "access-stream-id-12345", "lts_attack_stream_id": "attack-stream-id-12345"}, "created_at": 1619760096000, "updated_at": 1619760096000}' + headers: + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Mon, 12 May 2025 11:42:19 GMT + Server: + - api-gateway + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Request-Id: + - 2bc9734083ff7bc42e49dce25344025d + X-XSS-Protection: + - 1; mode=block; + status: + code: 200 + message: success +version: 1 diff --git a/tools/c7n_huaweicloud/tests/data/flights/waf_log_config_query b/tools/c7n_huaweicloud/tests/data/flights/waf_log_config_query new file mode 100644 index 000000000..1bd713c63 --- /dev/null +++ b/tools/c7n_huaweicloud/tests/data/flights/waf_log_config_query @@ -0,0 +1,52 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, zstd + Connection: + - keep-alive + Content-Type: + - application/json + Host: + - waf.ap-southeast-1.myhuaweicloud.com + User-Agent: + - huaweicloud-usdk-python/3.0 + X-Project-Id: + - ap-southeat-1 + X-Sdk-Date: + - 20250512T114219Z + method: GET + uri: https://waf.ap-southeast-1.myhuaweicloud.com/v1/ap-southeat-1/waf/config/lts + response: + body: + string: '{"id": "log-config-12345", "enabled": true, "ltsIdInfo": {"ltsGroupId": "group-id-12345", "ltsAccessStreamID": "access-stream-id-12345", "ltsAttackStreamID": "attack-stream-id-12345"}}' + headers: + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Mon, 12 May 2025 11:42:19 GMT + Server: + - api-gateway + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Request-Id: + - 002e342a9954f92875cd31785571b5c7 + X-XSS-Protection: + - 1; mode=block; + status: + code: 200 + message: success +version: 1 diff --git a/tools/c7n_huaweicloud/tests/data/flights/waf_log_config_update_disable b/tools/c7n_huaweicloud/tests/data/flights/waf_log_config_update_disable new file mode 100644 index 000000000..197a3eddf --- /dev/null +++ b/tools/c7n_huaweicloud/tests/data/flights/waf_log_config_update_disable @@ -0,0 +1,104 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, zstd + Connection: + - keep-alive + Content-Type: + - application/json + Host: + - waf.ap-southeast-1.myhuaweicloud.com + User-Agent: + - huaweicloud-usdk-python/3.0 + X-Project-Id: + - ap-southeat-1 + X-Sdk-Date: + - 20250512T114219Z + method: GET + uri: https://waf.ap-southeast-1.myhuaweicloud.com/v1/ap-southeat-1/waf/config/lts + response: + body: + string: '{"id": "test-log-config-id", "enabled": true, "lts_id_info": {"lts_group_id": "group-id-12345", "lts_access_stream_id": "access-stream-id-12345", "lts_attack_stream_id": "attack-stream-id-12345"}, "created_at": 1619760096000, "updated_at": 1619760096000}' + headers: + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Mon, 12 May 2025 11:42:19 GMT + Server: + - api-gateway + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Request-Id: + - 1a5248fe225cc1cad44fe237290bee02 + X-XSS-Protection: + - 1; mode=block; + status: + code: 200 + message: success +- request: + body: '{"enabled": false}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, zstd + Connection: + - keep-alive + Content-Length: + - '17' + Content-Type: + - application/json + Host: + - waf.ap-southeast-1.myhuaweicloud.com + User-Agent: + - huaweicloud-usdk-python/3.0 + X-Project-Id: + - ap-southeat-1 + X-Sdk-Date: + - 20250512T114220Z + method: PUT + uri: https://waf.ap-southeast-1.myhuaweicloud.com/v1/ap-southeat-1/waf/config/lts/test-log-config-id + response: + body: + string: '{"id": "test-log-config-id", "enabled": false, "lts_id_info": {"lts_group_id": "group-id-12345", "lts_access_stream_id": "access-stream-id-12345", "lts_attack_stream_id": "attack-stream-id-12345"}, "created_at": 1619760096000, "updated_at": 1619760196000}' + headers: + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Mon, 12 May 2025 11:42:20 GMT + Server: + - api-gateway + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Request-Id: + - 9cd2a51e2bdc7f92a427b518d1238b73 + X-XSS-Protection: + - 1; mode=block; + status: + code: 200 + message: success +version: 1 diff --git a/tools/c7n_huaweicloud/tests/data/flights/waf_log_config_update_enable b/tools/c7n_huaweicloud/tests/data/flights/waf_log_config_update_enable new file mode 100644 index 000000000..dfd0737e9 --- /dev/null +++ b/tools/c7n_huaweicloud/tests/data/flights/waf_log_config_update_enable @@ -0,0 +1,104 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, zstd + Connection: + - keep-alive + Content-Type: + - application/json + Host: + - waf.ap-southeast-1.myhuaweicloud.com + User-Agent: + - huaweicloud-usdk-python/3.0 + X-Project-Id: + - ap-southeat-1 + X-Sdk-Date: + - 20250512T114219Z + method: GET + uri: https://waf.ap-southeast-1.myhuaweicloud.com/v1/ap-southeat-1/waf/config/lts + response: + body: + string: '{"id": "test-log-config-id", "enabled": false, "lts_id_info": {"lts_group_id": "group-id-12345", "lts_access_stream_id": "access-stream-id-12345", "lts_attack_stream_id": "attack-stream-id-12345"}, "created_at": 1619760096000, "updated_at": 1619760096000}' + headers: + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Mon, 12 May 2025 11:42:19 GMT + Server: + - api-gateway + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Request-Id: + - 6ef5943b49acfc7c5492b328d011306f + X-XSS-Protection: + - 1; mode=block; + status: + code: 200 + message: success +- request: + body: '{"enabled": true, "lts_id_info": {"lts_group_id": "test-group-id", "lts_access_stream_id": "test-access-stream-id", "lts_attack_stream_id": "test-attack-stream-id"}}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, zstd + Connection: + - keep-alive + Content-Length: + - '156' + Content-Type: + - application/json + Host: + - waf.ap-southeast-1.myhuaweicloud.com + User-Agent: + - huaweicloud-usdk-python/3.0 + X-Project-Id: + - ap-southeat-1 + X-Sdk-Date: + - 20250512T114220Z + method: PUT + uri: https://waf.ap-southeast-1.myhuaweicloud.com/v1/ap-southeat-1/waf/config/lts/test-log-config-id + response: + body: + string: '{"id": "test-log-config-id", "enabled": true, "lts_id_info": {"lts_group_id": "test-group-id", "lts_access_stream_id": "test-access-stream-id", "lts_attack_stream_id": "test-attack-stream-id"}, "created_at": 1619760096000, "updated_at": 1619760196000}' + headers: + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Mon, 12 May 2025 11:42:20 GMT + Server: + - api-gateway + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Request-Id: + - 7fd1a52e3adc8f91b538a419e0237a82 + X-XSS-Protection: + - 1; mode=block; + status: + code: 200 + message: success +version: 1 diff --git a/tools/c7n_huaweicloud/tests/data/flights/waf_policy_query b/tools/c7n_huaweicloud/tests/data/flights/waf_policy_query new file mode 100644 index 000000000..b2954d9bc --- /dev/null +++ b/tools/c7n_huaweicloud/tests/data/flights/waf_policy_query @@ -0,0 +1,103 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, zstd + Connection: + - keep-alive + Content-Type: + - application/json + Host: + - waf.ap-southeast-1.myhuaweicloud.com + User-Agent: + - huaweicloud-usdk-python/3.0 + X-Project-Id: + - ap-southeat-1 + X-Sdk-Date: + - 20250512T114046Z + method: GET + uri: https://waf.ap-southeast-1.myhuaweicloud.com/v1/ap-southeat-1/waf/policy?page=1 + response: + body: + string: '{"total": 1, "items": [{"id": "policy-id-123456", "name": "policy-example", + "level": 2, "full_detection": true, "action": {"category": "block"}, "options": + {"webattack": true, "cc": true, "precise": false, "blacklist": true, "data_masking": + false, "antitamper": false, "antibot": false, "anticrawler": false, "geolocation_access_control": + false}, "hosts": ["domain-id-123456"], "bind_host": [{"id": "domain-id-123456", + "hostname": "example.com", "waf_type": "premium", "protect_status": 1}], "timestamp": + 1619760096000}]}' + headers: + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Mon, 12 May 2025 11:40:46 GMT + Server: + - api-gateway + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; + X-Content-Type-Options: + - nosniff + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Request-Id: + - e521a64f68ebbaea2c013d0806e0f8a1 + X-XSS-Protection: + - 1; mode=block; + status: + code: 200 + message: success +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, zstd + Connection: + - keep-alive + Content-Type: + - application/json + Host: + - waf.ap-southeast-1.myhuaweicloud.com + User-Agent: + - huaweicloud-usdk-python/3.0 + X-Project-Id: + - ap-southeat-1 + X-Sdk-Date: + - 20250512T115412Z + method: GET + uri: https://waf.ap-southeast-1.myhuaweicloud.com/v1/ap-southeat-1/waf/policy?page=2 + response: + body: + string: '{"total": 1, "items": []}' + headers: + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Mon, 12 May 2025 11:40:46 GMT + Server: + - api-gateway + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; + X-Content-Type-Options: + - nosniff + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Request-Id: + - e521a64f68ebbaea2c013d0806e0f8a1 + X-XSS-Protection: + - 1; mode=block; + status: + code: 200 + message: success diff --git a/tools/c7n_huaweicloud/tests/test_waf.py b/tools/c7n_huaweicloud/tests/test_waf.py new file mode 100644 index 000000000..80fe37db2 --- /dev/null +++ b/tools/c7n_huaweicloud/tests/test_waf.py @@ -0,0 +1,151 @@ +# Copyright The Cloud Custodian Authors. +# SPDX-License-Identifier: Apache-2.0 + +import json +import logging +from huaweicloud_common import BaseTest + + +class WafPolicyTest(BaseTest): + """Test class for Huawei Cloud WAF policy resources""" + + def test_policy_query(self): + """Test WAF policy list query""" + factory = self.replay_flight_data("waf_policy_query") + p = self.load_policy( + {"name": "waf-policy-query", "resource": "huaweicloud.waf-policy"}, + session_factory=factory, + ) + resources = p.run() + self.assertEqual(len(resources), 1) # Expect to return 1 policy + self.assertEqual(resources[0]["name"], "policy-example") # Verify policy name + self.assertEqual(resources[0]["level"], 2) # Verify protection level + self.assertTrue(resources[0]["full_detection"]) # Verify full detection mode is enabled + + +class WafLogConfigTest(BaseTest): + """Test class for Huawei Cloud WAF log configuration resources""" + + def test_log_config_query(self): + """Test WAF log configuration query""" + factory = self.replay_flight_data("waf_log_config_query") + p = self.load_policy( + {"name": "waf-log-config-query", "resource": "huaweicloud.waf-log-config"}, + session_factory=factory, + ) + resources = p.run() + + self.assertEqual(len(resources), 1) # Expect to return 1 log configuration + # Verify ID field exists + self.assertTrue("id" in resources[0]) + self.assertEqual(resources[0]["id"], "log-config-12345") + # Verify enabled status + self.assertTrue(resources[0]["enabled"]) + # Verify LTS information exists + self.assertTrue("ltsIdInfo" in resources[0]) + self.assertEqual(resources[0]["ltsIdInfo"]["ltsGroupId"], "group-id-12345") + + def test_log_config_enabled_filter_true(self): + """Test WAF log configuration enabled status filter - Enabled status""" + factory = self.replay_flight_data("waf_log_config_enabled_filter_true") + p = self.load_policy( + { + "name": "waf-log-config-enabled-true", + "resource": "huaweicloud.waf-log-config", + "filters": [ + { + "type": "enabled", + "value": True + } + ], + }, + session_factory=factory, + ) + resources = p.run() + self.assertEqual(len(resources), 1) # Expect to return 1 enabled log configuration + self.assertTrue(resources[0]["enabled"]) # Verify enabled status is True + self.assertEqual(resources[0]["id"], "log-config-12345") # Verify ID matches expected + + def test_log_config_enabled_filter_false(self): + """Test WAF log configuration enabled status filter - Disabled status""" + factory = self.replay_flight_data("waf_log_config_enabled_filter_false") + p = self.load_policy( + { + "name": "waf-log-config-enabled-false", + "resource": "huaweicloud.waf-log-config", + "filters": [ + { + "type": "enabled", + "value": False + } + ], + }, + session_factory=factory, + ) + resources = p.run() + self.assertEqual(len(resources), 1) # Expect to return 1 disabled log configuration + self.assertFalse(resources[0]["enabled"]) # Verify enabled status is False + self.assertEqual(resources[0]["id"], "log-config-12345") # Verify ID matches expected + + def test_update_log_config_enable(self): + """Test updating WAF log configuration - Enable logging""" + factory = self.replay_flight_data("waf_log_config_update_enable") + p = self.load_policy( + { + "name": "waf-log-config-update-enable", + "resource": "huaweicloud.waf-log-config", + "filters": [ + { + "type": "enabled", + "value": False + } + ], + "actions": [ + { + "type": "update", + "enabled": True, + "lts_id_info": { + "lts_group_id": "test-group-id", + "lts_access_stream_id": "test-access-stream-id", + "lts_attack_stream_id": "test-attack-stream-id" + } + } + ], + }, + session_factory=factory, + ) + resources = p.run() + self.assertEqual(len(resources), 1) # Expect to update 1 log configuration + # Confirm instance ID in record matches the instance ID being operated on + self.assertEqual(resources[0]["id"], "test-log-config-id") + # Initial state should be disabled + self.assertFalse(resources[0]["enabled"]) + + def test_update_log_config_disable(self): + """Test updating WAF log configuration - Disable logging""" + factory = self.replay_flight_data("waf_log_config_update_disable") + p = self.load_policy( + { + "name": "waf-log-config-update-disable", + "resource": "huaweicloud.waf-log-config", + "filters": [ + { + "type": "enabled", + "value": True + } + ], + "actions": [ + { + "type": "update", + "enabled": False + } + ], + }, + session_factory=factory, + ) + resources = p.run() + self.assertEqual(len(resources), 1) # Expect to update 1 log configuration + # Confirm instance ID in record matches the instance ID being operated on + self.assertEqual(resources[0]["id"], "test-log-config-id") + # Initial state should be enabled + self.assertTrue(resources[0]["enabled"])